我想创建collapsable
(或foldable
)句子。重要的是,它们应嵌入到段落中,并与文本的其余部分一起流动。我正在使用Java / Swing,JEditorpane
和DefaultStyledDocument
的扩展名。
例如:
[ - ]这是句子 一。 [ - ]这是
第二句。 [ - ]这是
第三句。
当第二句被折叠时,它变为:
[ - ]这是句子 一。 [+] [ - ]这是
第三句。
webpage有一个如何折叠文档区域的示例,但此区域未嵌入到段落中。我的问题是试图决定使用哪个View。
如果有人能给我一个指针(或者直觉)我最好能够将SentenceView作为基础(或者其他一些提示帮助我很长时间),我将非常感激。
答案 0 :(得分:0)
我会尝试回答我自己的问题。在对标准Java Swing类进行一些代码审查之后,我决定在FlowView
和ParagraphView
中实现我需要的大部分功能。值得注意的是,layoutRow
FlowStrategy
方法负责查找断点并提供文本行。在我的SentenceView
中,我必须或多或少完全相同。唯一的区别是我不想自己布局行。相反,我的SentenceView
需要实现breakView
,以便包含句子的段落可以将句子分成几部分并将其排除。不幸的是,我无法找到一种方法可以重新使用函数layoutRow
,最后我复制/粘贴了相当多的代码。
在我的(可折叠的)SentenceView
的实现过程中,我遇到了许多Java 7错误。例如,请参阅Java 7 line wrapping bug。此外,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;
}
...
isStart
我也有containsStart
,isEnd
有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
。其次,我使用MyLabelView
而不是LabelView
,主要是因为前面提到的换行错误。我还是Java和Java Swing的新手,我在Cloneable
接口上苦苦挣扎,clone
受保护且最终。所以,我刚刚实施createClone
来致电clone
。不是很优雅,但也不是我的主要担忧。我需要克隆,因为子视图在添加到Row
时会被重新分配。但是,如果在稍后阶段原始SentenceView
再次显示(因为不再需要断开),这会导致问题。我认为更好的方法可能是将所有子视图重新显示回this
,但我无法确定最佳位置。不过,我欢迎对此发表评论。另外,为了实际实现可折叠性,SentenceView
必须与创建的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
和preferredSize
将等于[+]标记的大小。 viewToModel
,modelToView
,getNextEastWestVisualPositionFrom
有一些摆弄。这与Folding (collapsible) area in the JEditorPane/JTextPane类似。我在代码中唯一更喜欢的是鼠标监听器中的部分,它检查事件是否发生在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();
}
};
希望这很有用。