Skip to content

Instantly share code, notes, and snippets.

@lqd
Created April 29, 2010 07:28
Show Gist options
  • Save lqd/383266 to your computer and use it in GitHub Desktop.
Save lqd/383266 to your computer and use it in GitHub Desktop.
package org.hybird.tests.ui;
import java.awt.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class ListViewTest
{
public static void main (String [] args)
{
SwingUtilities.invokeLater (new Runnable()
{
@Override
public void run ()
{
JFrame f = new JFrame ("Virtualized ListView");
f.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
JPanel main = new JPanel (new BorderLayout ());
ListView list = new ListView ();
list.setProvider (new LabeledCellProvider());
list.setRenderer (new StripingCellRenderer());
list.setModel (new ListViewModel (100));
list.setPreferredSize (new Dimension (640, 480));
main.add (list, BorderLayout.CENTER);
f.setContentPane (main);
f.pack ();
f.setLocationRelativeTo (null);
f.setVisible (true);
}
});
}
public static abstract class CellProvider
{
private List<Cell> availableCells, usedCells;
public CellProvider()
{
availableCells = new ArrayList<Cell>();
usedCells = new ArrayList<Cell>();
}
public abstract Cell createCell (ListView list);
public Cell getUnusedCell (ListView list)
{
if (availableCells.isEmpty())
{
Cell cell = createCell (list);
list.view.add (cell);
markCellAvailable (list, cell);
}
return availableCells.get (0);
}
public void markCellUsed (ListView list, Cell cell)
{
usedCells.add (cell);
availableCells.remove (cell);
}
public void markCellAvailable (ListView list, Cell cell)
{
availableCells.add (cell);
usedCells.remove (cell);
}
public void markAllUsedCellsAvailable ()
{
for (int i = usedCells.size(); i-- > 0; )
{
Cell usedCell = usedCells.get (i);
markCellAvailable (null, usedCell);
}
usedCells.clear();
}
}
public static class LabeledCell extends Cell
{
private JLabel label;
public void setLabel (String text)
{
if (label == null)
{
label = new JLabel ();
add (label);
}
label.setText (text);
}
}
public static class LabeledCellProvider extends CellProvider
{
@Override
public Cell createCell (ListView list)
{
return new LabeledCell ();
}
}
public static class StripingCellRenderer implements CellRenderer
{
private Color [] colors = {Color.orange, Color.yellow};
@Override
public Cell renderCell (ListView list, Cell cell, Object value, int row)
{
LabeledCell x = (LabeledCell) cell;
cell.setBackground (colors [row % colors.length]);
x.setLabel (value.toString ());
cell.setPreferredSize (new Dimension (-1, x.label.getPreferredSize().height + row));
// if (row % 3 == 0)
// list.setCellHeight (row, 50);
return cell;
}
}
public static interface CellRenderer
{
Cell renderCell (ListView list, Cell cell, Object value, int row);
}
public static class ListView extends JScrollPane
{
private JPanel view;
private CellProvider provider;
public ListView ()
{
super (JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
setBorder (new EmptyBorder (new Insets (0, 0, 0, 0)));
view = new JPanel (null);
view.setBackground (Color.green);
setViewportView (view);
provider = new CellProvider()
{
@Override
public Cell createCell (ListView list)
{
return new Cell();
}
};
}
public void setProvider (CellProvider provider)
{
this.provider = provider;
}
private ListViewport viewport;
@Override
protected JViewport createViewport ()
{
viewport = new ListViewport();
return viewport;
}
private class ListViewport extends JViewport
{
private Point viewPosition;
@Override
public void setViewPosition (Point p)
{
super.setViewPosition (p);
boolean changed = true;
if (viewPosition == null)
viewPosition = p;
else
{
if (viewPosition.x != p.x || viewPosition.y != p.y)
{
viewPosition.x = p.x;
viewPosition.y = p.y;
}
else
changed = false;
}
if (changed)
layoutComponents ();
}
}
private int width, height;
@Override
public void setBounds (int x, int y, int width, int height)
{
boolean changed = this.width != width || this.height != height;
this.width = width;
this.height = height;
super.setBounds (x, y, width, height);
if (changed)
{
layoutComponents();
}
}
private CellRenderer renderer;
public void setRenderer (CellRenderer renderer)
{
this.renderer = renderer;
}
private static class LayoutInfo
{
public int y, height;
}
private ListModel model;
private Map<Integer, LayoutInfo> layoutInfos;
public void setModel (ListModel model)
{
this.model = model;
layoutInfos = new HashMap<Integer, LayoutInfo> ();
}
public LayoutInfo setCellHeight (int row, int height)
{
LayoutInfo info = layoutInfos.get (row);
if (info == null)
{
info = new LayoutInfo ();
layoutInfos.put (row, info);
}
info.height = height;
return info;
}
private int knownHeight ()
{
int height = 0;
for (int row : layoutInfos.keySet())
height += layoutInfos.get (row).height;
return height;
}
private Dimension tmpDimension;
private void layoutComponents ()
{
if (viewport.viewPosition == null)
return;
System.out.println ("");
if (tmpDimension == null)
tmpDimension = new Dimension();
provider.markAllUsedCellsAvailable();
int size = model.getSize ();
int y = 0;
int row = 0;
for ( ; row < size; ++row)
{
LayoutInfo info = layoutInfos.get (row);
if (row == 0 && info == null)
break;
if (info == null)
{
System.out.println("Layout info is null for row " + row + ", pos: "
+ viewport.viewPosition.y + ", fake row: " + (viewport.viewPosition.y / 16));
row = Math.min (viewport.viewPosition.y / 16 - 1, size);
y = viewport.viewPosition.y;
// ds ce cas ci, il faudrait plutot setter le thumb au max et partir par la fin
break;
}
if (info.y + info.height > viewport.viewPosition.y)
{
y = info.y;
break;
}
}
// row = Math.max (row - 2, 0);
// if (layoutInfos.containsKey (row))
// y = layoutInfos.get (row).y;
System.out.println ("view pos: " + viewport.viewPosition + ", row: " + row + ", y: " + y);
int i = row;
for ( ; i < size; ++i)
{
System.out.println ("1Laying out row " + i);
Cell cell = provider.getUnusedCell (this);
renderer.renderCell (this, cell, model.getElementAt (i), i);
provider.markCellUsed (this, cell);
LayoutInfo info = layoutInfos.get (i);
if (info == null)
{
cell.getPreferredSize (tmpDimension);
info = setCellHeight (i, tmpDimension.height);
info.y = y;
}
cell.setBounds (0, y, width, info.height);
if (y + info.height > viewport.viewPosition.y + height)
{
if (row == 0)
{
System.out.println ("ay: " + y + ", height: " + height + ", row: " + i
+ ", size: " + size + ", hidden: " + (size - i) + ", component count: " + view.getComponentCount ());
tmpDimension.width = width;
tmpDimension.height = knownHeight() + (size - layoutInfos.size() - 1) * 32;
System.out.println("setting view1: " + tmpDimension);
view.setPreferredSize (tmpDimension);
}
++i;
y += info.height;
break;
}
y += info.height;
}
if (row != 0)
{
System.out.println ("Can layout " + row + " rows from row " + i + " to the bottom: " + y + ", viewpos: " + viewport.viewPosition.y);
int max = Math.min (i + row, size);
for ( ; i < max; ++i)
{
System.out.println ("2Laying out row " + i);
Cell cell = provider.getUnusedCell (this);
renderer.renderCell (this, cell, model.getElementAt (i), i);
provider.markCellUsed (this, cell);
LayoutInfo info = layoutInfos.get (i);
if (info == null)
{
cell.getPreferredSize (tmpDimension);
info = setCellHeight (i, tmpDimension.height);
info.y = y;
}
cell.setBounds (0, y, width, info.height);
if (y + info.height > viewport.viewPosition.y + height)
{
System.out.println ("by: " + y + ", height: " + height + ", row: " + i
+ ", size: " + size + ", hidden: " + (size - i) + ", component count: " + view.getComponentCount ()
+ ", max: " + max);
tmpDimension.width = width;
tmpDimension.height = y + (size - i) * 32;
tmpDimension.height = knownHeight() + (size - layoutInfos.size()) * 32;
//System.out.println("setting view2: " + tmpDimension);
view.setPreferredSize (tmpDimension);
break;
}
y += info.height;
}
}
validate();
}
}
public static class Cell extends JPanel
{
private Dimension preferredSize;
public Cell ()
{
setLayout (new GridLayout());
}
@Override
public void setPreferredSize (Dimension preferredSize)
{
this.preferredSize = preferredSize;
super.setPreferredSize (preferredSize);
}
public Dimension getPreferredSize (Dimension preferredSize)
{
preferredSize.width = this.preferredSize.width;
preferredSize.height = this.preferredSize.height;
return preferredSize;
}
}
public static class ListViewModel extends AbstractListModel
{
private int size;
public ListViewModel (int size)
{
this.size = size;
}
@Override
public Object getElementAt (int index)
{
return "row " + index;
}
@Override
public int getSize ()
{
return size;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment