我会尝试回答我自己的问题。在对标准 Java Swing 类进行了一些代码审查后,我决定我需要的大部分功能都在FlowView
和ParagraphView
. 值得注意的是,有一种layoutRow
方法FlowStrategy
负责查找断点并提供文本行。在我的SentenceView
情况下,我必须或多或少完全相同。唯一的区别是我不想自己布局行。相反,我SentenceView
需要实现breakView
,以便包含该句子的段落可以将句子切成碎片并进行布局。不幸的是,我找不到可以重用该功能的方法layoutRow
,最终我复制/粘贴了相当多的代码。
在我的(可折叠的)实现过程中,我SentenceView
遇到了一些 Java 7 错误。例如,请参阅Java 7 换行错误。此外,在 Java 中,minimumSize 是基于空格处的断点(这是很好的断点权重)还是字符处的断点(这是很好的断点权重)似乎是模棱两可的。GlyphView
并LabelView
在出色的断点权重的基础上给出它们的 minimumSize,但ParagraphView
将其与任何断点混合都比BadBreakWeight
(参见代码calculateMinorAxisRequirements
)更好。这相当令人困惑。
无论如何,让我强调一下我实现SentenceView
. 我省略了实际的可折叠部分,因为它有点繁琐,而不是我正在努力解决的部分。我将专注于breakView
功能。
public class SentenceView extends BoxView {
boolean containsStart;
boolean containsEnd;
public SentenceView(Element elem) {
super(elem, View.X_AXIS);
containsStart = true;
containsEnd = true;
}
...
我也有一个 getter isStart
forcontainsStart
和isEnd
for containsEnd
。正如我所说,该breakView
方法主要是复制的:
public View breakView(int axis, int p0, float pos, float len) {
if (axis == View.Y_AXIS) return this;
if (p0 == getStartOffset() && len >= getPreferredSpan(axis)) return this;
float spanLeft = len;
int p = p0;
float x = pos;
int end = getEndOffset();
Row row = new Row(getElement());
TabExpander te = (this instanceof TabExpander) ? (TabExpander)this : null;
int breakWeight = BadBreakWeight;
float breakX = 0f;
float breakSpan = 0f;
int breakIndex = -1;
int n = 0;
if (p0 == getStartOffset() && isStart()) row.setContainsStart(true);
Vector<View> viewBuffer = new Vector<View>(10, 10);
while (p < end && spanLeft >= 0) {
View v = createView(p);
if (v == null) break;
int bw = v.getBreakWeight(axis, x, spanLeft);
if (bw >= ForcedBreakWeight) {
View w = v.breakView(axis, p, x, spanLeft);
if (w != null) {
viewBuffer.add(w);
} else if (n == 0) {
viewBuffer.add(v);
}
break;
} else if (bw >= breakWeight && bw > BadBreakWeight) {
breakWeight = bw;
breakX = x;
breakSpan = spanLeft;
breakIndex = n;
}
float chunkSpan;
if (v instanceof TabableView) {
chunkSpan = ((TabableView)v).getTabbedSpan(x, te);
} else {
chunkSpan = v.getPreferredSpan(axis);
}
if (isEnd() && v.getEndOffset() == end) {
if ((chunkSpan <= spanLeft) && (chunkSpan > spanLeft)) {
spanLeft = chunkSpan - 1;
}
}
if (chunkSpan > spanLeft && breakIndex >= 0) {
if (breakIndex < n) {
v = viewBuffer.get(breakIndex);
}
for (int i = n - 1; i >= breakIndex; i--) {
viewBuffer.remove(i);
}
v = v.breakView(axis, v.getStartOffset(), breakX, breakSpan);
}
spanLeft -= chunkSpan;
x += chunkSpan;
viewBuffer.add(v);
p = v.getEndOffset();
n++;
}
// cloning:
Vector<View> viewBuffer2 = new Vector<>();
for (int j = 0; j < viewBuffer.size(); j++) {
View v = viewBuffer.get(j);
if (v instanceof MyLabelView) {
v = (View) ((MyLabelView)v).createClone();
} else {
throw new RuntimeException("SentenceView can only contain MyLabelView");
}
viewBuffer2.add(v);
}
View[] views = new View[viewBuffer2.size()];
viewBuffer2.toArray(views);
row.replace(0, row.getViewCount(), views);
return row;
}
这里有几点需要注意。首先,breakView
将返回自身或SentenceView.Row
. 其次,我使用的是 aMyLabelView
而不是LabelView
,主要是因为前面提到的换行错误。我还是 Java 和 Java Swing 的新手,我在Cloneable
接口方面有点挣扎,并且clone
是受保护的和最终的。所以,我只是实现createClone
调用clone
. 不是很优雅,但也不是我的主要担心。我需要克隆,因为子视图在添加到Row
. 但是,如果在稍后阶段SentenceView
再次显示原始文件(因为不再需要中断),这会导致问题。我认为更好的方法可能是将所有子视图重新设置为this
但我不知道在哪里最好地做到这一点。不过,我欢迎对此发表评论。此外,为了实际实现可折叠性,SentenceView
必须与 created 共享其可折叠状态Rows
。我通过在一个对象中包装一个布尔值然后共享该对象来做到这一点。
我将给出以下不带代码的方法,因为它们不太难或可以从ParagraphView
.
protected SizeRequirements calculateMajorAxisRequirements(int axis,
SizeRequirements r)
protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r)
public View createFragment(int p0, int p1)
protected View createView(int startOffset)
protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
baselineLayout(targetSpan, axis, offsets, spans);
}
最后,还有Row
内部类:
class Row extends SentenceView {
Row(Element elem) {
super(elem);
containsStart = false;
containsEnd = false;
}
public float getAlignment(int axis) {
if (axis == View.X_AXIS) return 0;
return super.getAlignment(axis);
}
public AttributeSet getAttributes() {
View p = getParent();
return (p != null) ? p.getAttributes() : null;
}
protected int getViewIndexAtPosition(int pos) {
if(pos < getStartOffset() || pos >= getEndOffset())
return -1;
for(int counter = getViewCount() - 1; counter >= 0; counter--) {
View v = getView(counter);
if(pos >= v.getStartOffset() &&
pos < v.getEndOffset()) {
return counter;
}
}
return -1;
}
protected void loadChildren(ViewFactory f) {
}
}
这样可行。有点不幸的是,我最终复制了相当多的现有代码,但对我来说如何breakView
在 X 轴上实现paragraphView
或如何找到另一种方法来做到这一点并不明显。此外,为了实现可折叠性,还需要做更多的工作。该paint
方法需要更改为在前面绘制 [-](如果containsStart
为真)。并且需要考虑到这额外的空间(我为此使用了插图)minimumSize
。preferredSize
它创建的SentenceView
和Rows
它需要共享折叠状态。如果视图被折叠,minimumSize
andpreferredSize
将等于 [+] 标记的大小。有一些摆弄viewToModel
, modelToView
, getNextEastWestVisualPositionFrom
。这都类似于JEditorPane/JTextPane 中的折叠(可折叠)区域。在我的代码中,我唯一喜欢的一点是鼠标侦听器中检查事件是否发生在 a 内部的部分SentenceView
,因为我避免使用viewToModel
. 所以,让我也给它:
MouseListener sentenceCollapse = new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
JEditorPane src = (JEditorPane)e.getSource();
View v = src.getUI().getRootView(src);
Insets ins = src.getInsets();
int x = ins.left;
int y = ins.top;
Point p = e.getPoint();
p.translate(-x, -y);
Rectangle alloc = new Rectangle(0,0,Short.MAX_VALUE,Short.MAX_VALUE);
while (v != null && !(v instanceof SentenceView)) {
View vChild = null;
int n = v.getViewCount();
for (int i = 0; i < n; i++) {
Rectangle r = v.getChildAllocation(i, alloc).getBounds();
if (r.contains(p)) {
vChild = v.getView(i);
alloc = (Rectangle) r.clone();
}
}
v = vChild;
}
if (v != null && v instanceof SentenceView) {
<<< unfold or fold the Sentence View >>>
src.repaint();
}
};
希望这是有用的。