问题描述
为了实现聊天内容展示功能,可以考虑使用一个JPanel,使用BoxLayout布局,axis(轴)设置成Y_AXIS,就可以实现聊天内容从上到下的一个展示效果,每个聊天内容占一行。
但如果Panel本身高度较大,前期添加Component的时候,Component的高度会被拉伸到JPanel的剩余可用高度。
假设使用BoxLayout的JPanel高度为700,放置在一个JScrollPane中。
- 新加一个高度为200的组件,会被拉长到700。
- 新增一个高度为300的组件,第一个组件高度被压缩成350,第二个组件高度为350
- 新增一个高度为200的组件,第一个组件高度被压缩成200,第二个组件高度被压缩成300,第三个组件高度为200 ,开始出现垂直滚动条。(此时开始所有组件显示高度都变正常)
原因分析
BoxLayout在排列组件的时候,会比较Container内的组件高度与Container自身的高度,如果Container自身的高度比组件的高度大,则会拉伸组件,否则会挤压组件。
// BoxLayout.class target为使用BoxLayout的Container
public void layoutContainer(Container target) {
// 计算container内所有组件请求的宽度和高度(min, prefered, max)
checkRequests();
if (absoluteAxis == X_AXIS) {
} else {
// 使用垂直轴方向的布局时,走这个逻辑
SizeRequirements.calculateAlignedPositions(alloc.width, xTotal, xChildren, xOffsets,xSpans, ltr);
// 计算平铺方式的位置,计算出来的ySpans数据就是每个组件的展示高度
SizeRequirements.calculateTiledPositions(alloc.height, yTotal, yChildren, yOffsets,ySpans);
}
}
// SizeRequirements.class
public static void calculateTiledPositions(...) {
// 计算container内所有组件请求的大小之和,这里传进来的是高度
long min = 0;
long pref = 0;
long max = 0;
for (int i = 0; i < children.length; i++) {
min += children[i].minimum;
pref += children[i].preferred;
max += children[i].maximum;
}
// 如果container目前的高度大于所有子组件的prefered高度,这里就调用expandTile方法去拉伸组件的高度了
if (allocated >= pref) {
expandedTile(allocated, min, pref, max, children, offsets, spans, forward);
} else {
compressedTile(allocated, min, pref, max, children, offsets, spans, forward);
}
}
private static void expandedTile(...) {
float totalPlay = Math.min(allocated - pref, max - pref);
// 得到乘积因子
float factor = (max - pref == 0) ? 0.0f : totalPlay / (max - pref);
for (int i = 0; i < spans.length; i++) {
// 拉伸的距离为:乘积因子 * (组件的最大高度 - 组件的prefered)
int play = (int)(factor * (req.maximum - req.preferred));
spans[i] = (int) Math.min((long) req.preferred + (long) play, Integer.MAX_VALUE);
}
}
可以看到,只要组件的最大高度大于当前组件的prefered,且container还有剩余空间时,就会被拉伸。
解决方案
临时方案
由于具体拉伸时,乘积因子用的是组件的max - prefered,只要我们重写要添加的组件的getMaximumSize方法,让它返回的高度跟prefered一致,就不会被拉伸。
public class XxxComponent {
@Overide
public Dimension getMaximumSize() {
return new Dimension(Short.MAX_VALUE, getPreferredSize().height);
}
}
推荐方案
修改BoxLayout的checkRequests方法,获取组件对应的SizeRequirements对象时,将maximum设置成prefered
void checkRequests() {
xChildren = new SizeRequirements[n];
yChildren = new SizeRequirements[n];
for (int i = 0; i < n; i++) {
...
Dimension min = c.getMinimumSize();
Dimension typ = c.getPreferredSize();
Dimension max = c.getMaximumSize();
// yChildren[i] = new SizeRequirements(min.height, typ.height, max.height, c.getAlignmentY());
yChildren[i] = new SizeRequirements(min.height, typ.height, typ.height, c.getAlignmentY());
}
}
由于BoxLayout中checkRequests方法修饰符为default,无法通过继承的方式重写改方法,只能新建一个类,复制BoxLayout的实现,并修改指定位置的代码。