package android.widget; import java.awt.Component; import java.awt.Container; import java.io.Writer; import javax.swing.Spring; import javax.swing.SpringLayout; import com.applang.Util; import com.applang.Util.ValMap; import android.content.Context; import android.content.res.Resources; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import static com.applang.Util.*; import static com.applang.Util2.*; public class RelativeLayout extends ViewGroup { /** * Rule that aligns a child's right edge with another child's left edge. */ public static final int LEFT_OF = 0; /** * Rule that aligns a child's left edge with another child's right edge. */ public static final int RIGHT_OF = 1; /** * Rule that aligns a child's bottom edge with another child's top edge. */ public static final int ABOVE = 2; /** * Rule that aligns a child's top edge with another child's bottom edge. */ public static final int BELOW = 3; /** * Rule that aligns a child's baseline with another child's baseline. */ public static final int ALIGN_BASELINE = 4; /** * Rule that aligns a child's left edge with another child's left edge. */ public static final int ALIGN_LEFT = 5; /** * Rule that aligns a child's top edge with another child's top edge. */ public static final int ALIGN_TOP = 6; /** * Rule that aligns a child's right edge with another child's right edge. */ public static final int ALIGN_RIGHT = 7; /** * Rule that aligns a child's bottom edge with another child's bottom edge. */ public static final int ALIGN_BOTTOM = 8; /** * Rule that aligns the child's left edge with its RelativeLayout * parent's left edge. */ public static final int ALIGN_PARENT_LEFT = 9; /** * Rule that aligns the child's top edge with its RelativeLayout * parent's top edge. */ public static final int ALIGN_PARENT_TOP = 10; /** * Rule that aligns the child's right edge with its RelativeLayout * parent's right edge. */ public static final int ALIGN_PARENT_RIGHT = 11; /** * Rule that aligns the child's bottom edge with its RelativeLayout * parent's bottom edge. */ public static final int ALIGN_PARENT_BOTTOM = 12; /** * Rule that centers the child with respect to the bounds of its * RelativeLayout parent. */ public static final int CENTER_IN_PARENT = 13; /** * Rule that centers the child horizontally with respect to the * bounds of its RelativeLayout parent. */ public static final int CENTER_HORIZONTAL = 14; /** * Rule that centers the child vertically with respect to the * bounds of its RelativeLayout parent. */ public static final int CENTER_VERTICAL = 15; private static final int VERB_COUNT = 16; public RelativeLayout(Context context, Object...params) { super(context); if (param(null, 0, params) instanceof Container) setComponent((Component) params[0]); } public static class LayoutParams extends ViewGroup.MarginLayoutParams { @Override public String toString() { Writer writer = write(null, super.toString()); for (int i = 0; i < VERB_COUNT; i++) if (notNullOrEmpty(mRules[i])) { String attr = attributeName(i); attr = attr.substring(1 + attr.indexOf(':')); writer = write_assoc(writer, attr, mRules[i], 1); } return writer.toString(); } public LayoutParams(int width, int height) { super(width, height); } public boolean alignWithParent; public String[] mRules = new String[VERB_COUNT]; private String attributeName(int verb) { switch (verb) { case ALIGN_PARENT_TOP: return "android:layout_alignParentTop"; case ALIGN_PARENT_BOTTOM: return "android:layout_alignParentBottom"; case ALIGN_PARENT_LEFT: return "android:layout_alignParentLeft"; case ALIGN_PARENT_RIGHT: return "android:layout_alignParentRight"; case ALIGN_LEFT: return "android:layout_alignLeft"; case ALIGN_RIGHT: return "android:layout_alignRight"; case ALIGN_BOTTOM: return "android:layout_alignBottom"; case ALIGN_TOP: return "android:layout_alignTop"; case ABOVE: return "android:layout_above"; case BELOW: return "android:layout_below"; case LEFT_OF: return "android:layout_toLeftOf"; case RIGHT_OF: return "android:layout_toRightOf"; case ALIGN_BASELINE: return "android:layout_alignBaseline"; case CENTER_IN_PARENT: return "android:layout_centerInParent"; case CENTER_HORIZONTAL: return "android:layout_centerHorizontal"; case CENTER_VERTICAL: return "android:layout_centerVertical"; default: return null; } } private int verb(String attributeName) { for (int i = 0; i < VERB_COUNT; i++) if (attributeName(i).equals(attributeName)) return i; return -1; } public LayoutParams() { super(FILL_PARENT, WRAP_CONTENT); } public LayoutParams(Context context, AttributeSet attrs) { super(context, attrs); for (int i = 0; i < VERB_COUNT; i++) { String anchor = attrs.getAttributeValue(attributeName(i)); if (anchor != null) addRule(context, i, anchor); } } public void addRules(Context context, Object...attrs) { ValMap map = Util.namedParams(attrs); for (String key : map.keySet()) { int verb = verb(key); if (verb > -1) addRule(context, verb, stringValueOf(map.get(key))); } } public void addRule(Context context, int verb, String anchor) { addRule(verb, Resources.textValue(context, anchor)); } public void addRule(int verb, String anchor) { mRules[verb] = anchor; } public String[] getRules() { return mRules; } } public void setLayoutParams(ViewGroup.LayoutParams layoutParams) { super.setLayoutParams(layoutParams); } @Override public Container getContainer() { Container container = super.getContainer(); if (!(container.getLayout() instanceof SpringLayout)) container.setLayout(new SpringLayout()); return container; } public SpringLayout getLayout() { return (SpringLayout) getContainer().getLayout(); } public void putConstraint(String edge, Component component, int pad, String edge2, Component component2) { printEdges(component.getName() + " before", getLayout().getConstraints(component), edge); getLayout().putConstraint(edge, component, pad, edge2, component2); printEdges(component.getName() + " after", getLayout().getConstraints(component), edge); } private void alignWithAnchor(View parent, String name, int verb, View view, boolean init) { View anchor = parent.findViewWithTag(name); if (anchor != null && anchor.getComponent() != null) { if (init) { int viewIndex = views.indexOf(view); int anchorIndex = views.indexOf(anchor); switch (verb) { case LEFT_OF: indexMove(viewIndex, anchorIndex); case RIGHT_OF: indexMove(viewIndex, anchorIndex + 1); break; case ABOVE: indexMove(viewIndex, anchorIndex); break; case BELOW: indexMove(viewIndex, anchorIndex + 1); break; } } else { SpringLayout.Constraints coCons = getLayout().getConstraints(view.getComponent()); SpringLayout.Constraints anCons = getLayout().getConstraints(anchor.getComponent()); String coEdge = "", anEdge = ""; switch (verb) { case LEFT_OF: coEdge = SpringLayout.EAST; anEdge = SpringLayout.WEST; break; case RIGHT_OF: coEdge = SpringLayout.WEST; anEdge = SpringLayout.EAST; break; case ABOVE: coEdge = SpringLayout.SOUTH; anEdge = SpringLayout.NORTH; break; case BELOW: coEdge = SpringLayout.NORTH; anEdge = SpringLayout.SOUTH; break; default: return; } int pad = Math.abs(coCons.getConstraint(coEdge).getValue() - anCons.getConstraint(anEdge).getValue()); putConstraint(coEdge, view.getComponent(), pad, anEdge, anchor.getComponent()); } } } private void alignEdges(ViewGroup parent, String name, int verb, View view) { View anchor = parent.findViewWithTag(name); if (anchor != null && anchor.getComponent() != null) { SpringLayout.Constraints coCons = getLayout().getConstraints(view.getComponent()); SpringLayout.Constraints anCons = getLayout().getConstraints(anchor.getComponent()); switch (verb) { case ALIGN_LEFT: coCons.setConstraint(SpringLayout.WEST, anCons.getConstraint(SpringLayout.WEST)); break; case ALIGN_TOP: coCons.setConstraint(SpringLayout.NORTH, anCons.getConstraint(SpringLayout.NORTH)); break; case ALIGN_RIGHT: coCons.setConstraint(SpringLayout.EAST, anCons.getConstraint(SpringLayout.EAST)); break; case ALIGN_BOTTOM: coCons.setConstraint(SpringLayout.SOUTH, anCons.getConstraint(SpringLayout.SOUTH)); break; } } } private void alignWithParent(ViewGroup parent, int verb, View view) { SpringLayout.Constraints parCons = getLayout().getConstraints(parent.getContainer()); SpringLayout.Constraints coCons = getLayout().getConstraints(view.getComponent()); String edge = ""; switch (verb) { case ALIGN_PARENT_LEFT: edge = SpringLayout.WEST; break; case ALIGN_PARENT_TOP: edge = SpringLayout.NORTH; break; case ALIGN_PARENT_RIGHT: edge = SpringLayout.EAST; break; case ALIGN_PARENT_BOTTOM: edge = SpringLayout.SOUTH; break; default: return; } int pad = Math.abs(parCons.getConstraint(edge).getValue() - coCons.getConstraint(edge).getValue()); putConstraint(edge, view.getComponent(), pad, edge, parent.getContainer()); } public void doRules(boolean init, View view) { ViewGroup parent = (ViewGroup) view.getParent(); Component component = view.getComponent(); ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); if (layoutParams instanceof LayoutParams && component != null) { LayoutParams relParams = (LayoutParams) layoutParams; String[] rules = relParams.getRules(); for (int verb = 0; verb < rules.length; verb++) { String rule = rules[verb]; if (notNullOrEmpty(rule)) { switch (verb) { case LEFT_OF: case RIGHT_OF: case ABOVE: case BELOW: alignWithAnchor(parent, rule, verb, view, init); break; case ALIGN_LEFT: case ALIGN_TOP: case ALIGN_RIGHT: case ALIGN_BOTTOM: if (!init) alignEdges(parent, rule, verb, view); break; case ALIGN_PARENT_LEFT: case ALIGN_PARENT_TOP: case ALIGN_PARENT_RIGHT: case ALIGN_PARENT_BOTTOM: if (!init) alignWithParent(parent, verb, view); break; case ALIGN_BASELINE: break; case CENTER_HORIZONTAL: break; case CENTER_VERTICAL: break; case CENTER_IN_PARENT: break; } } } } } private ValList index = null; private void indexInit() { index = vlist(); for (int i = 0; i < getChildCount(); i++) index.add(i); } private int index(int index) { return index < 0 || index >= this.index.size() ? -1 : (Integer) this.index.get(index); } private void indexMove(int from, int to) { index.add(to, index.remove(from)); } @Override public void preLayout() { indexInit(); for (int i = 0; i < getChildCount(); i++) doRules(true, getChildAt(i)); } @Override public Container doLayout() { arrange(1, index.size()); // printSprings(); for (int i = 0; i < getChildCount(); i++) doRules(false, getChildAt(index(i))); // printSprings(); return getContainer(); } public void arrange(int rows, int cols, int...xyPadxPady) { SpringLayout layout = getLayout(); Spring x = Spring.constant(param(0,0,xyPadxPady)), y = Spring.constant(param(0,1,xyPadxPady)); Spring xPad = Spring.constant(param(0,2,xyPadxPady)), yPad = Spring.constant(param(0,3,xyPadxPady)); boolean oneRow = rows < 2; boolean oneCol = cols < 2; View view = null; for (int c = 0; c < cols; c++) { Spring width = Spring.constant(0); Spring dx = width; for (int r = 0; r < rows; r++) { view = getViewAt(r,c,cols); if (view == null) continue; Spring w = getLayout().getConstraints(view.getComponent()).getWidth(); width = Spring.max(width, w); dx = oneRow || oneCol ? Spring.max(dx, Spring.sum(w, getMarginSpring(view, "horz"))) : width; } for (int r = 0; r < rows; r++) { view = getViewAt(r,c,cols); if (view == null) continue; SpringLayout.Constraints constraints = getLayout().getConstraints(view.getComponent()); Spring z = x; if (oneRow || oneCol) z = Spring.sum(z, getMarginSpring(view, SpringLayout.WEST)); constraints.setX(z); if (oneRow || oneCol) z = Spring.sum(z, getMarginSpring(view, SpringLayout.EAST, width.getValue())); if (!oneCol) constraints.setWidth(width); if (oneRow) x = z; } x = Spring.sum(x, Spring.sum(dx, xPad)); } for (int r = 0; r < rows; r++) { Spring height = Spring.constant(0); Spring dy = height; for (int c = 0; c < cols; c++) { view = getViewAt(r,c,cols); if (view == null) continue; Spring h = getLayout().getConstraints(view.getComponent()).getHeight(); height = Spring.max(height, h); dy = oneCol || oneRow ? Spring.max(dy, Spring.sum(h, getMarginSpring(view, "vert"))) : height; } for (int c = 0; c < cols; c++) { view = getViewAt(r,c,cols); if (view == null) continue; SpringLayout.Constraints constraints = getLayout().getConstraints(view.getComponent()); Spring z = y; if (oneCol || oneRow) z = Spring.sum(z, getMarginSpring(view, SpringLayout.NORTH)); constraints.setY(z); if (oneCol || oneRow) z = Spring.sum(z, getMarginSpring(view, SpringLayout.SOUTH, height.getValue())); if (!oneRow) constraints.setHeight(height); if (oneCol) y = z; } y = Spring.sum(y, Spring.sum(dy, yPad)); } SpringLayout.Constraints cons = layout.getConstraints(getContainer()); cons.setConstraint(SpringLayout.SOUTH, y); cons.setConstraint(SpringLayout.EAST, x); // println("width", cons.getWidth().getValue(), x.getValue()); // println("height", cons.getHeight().getValue(), y.getValue()); } private View getViewAt(int row, int col, int cols) { return getChildAt(index(row * cols + col)); } private Spring getMarginSpring(View view, String edge, int...more) { int margin = 0; if (view != null && view.getLayoutParams() instanceof MarginLayoutParams) { MarginLayoutParams margs = (MarginLayoutParams) view.getLayoutParams(); if ("horz".equals(edge)) { margin += margs.getMargin(SpringLayout.WEST); margin += margs.getMargin(SpringLayout.EAST); } else if ("vert".equals(edge)) { margin += margs.getMargin(SpringLayout.NORTH); margin += margs.getMargin(SpringLayout.SOUTH); } else margin = margs.getMargin(edge); } return Spring.constant(margin, margin, margin + param(0, 0, more)); } public void printSprings() { for (int i = 0; i < getChildCount(); i++) { View v = getChildAt(index(i)); printSprings(stringValueOf(v.getTag()), v); } } public void printSprings(String string, View v) { SpringLayout.Constraints cons = getLayout().getConstraints(v.getComponent()); printEdges(string, cons, all_constraints()); } private static void printEdges(String string, SpringLayout.Constraints cons, String...edges) { Writer writer = write(null, string); for (String edge : edges) { writeConstraint(writer, cons, edge); } println(writer.toString()); } private static Writer writeConstraint(Writer writer, SpringLayout.Constraints cons, String edge) { Spring constraint = cons.getConstraint(edge); String s = stringValueOf(constraint); s = s.replaceAll("javax.swing.Spring\\$", ""); return write_assoc(writer, edge, s, 3); } private static String[] all_constraints() { String[] edges = getPrivateField(SpringLayout.class, null, "ALL_HORIZONTAL"); edges = arrayappend(edges, (String[]) getPrivateField(SpringLayout.class, null, "ALL_VERTICAL")); return edges; } public static void setOuterMargins(Container container, MarginLayoutParams margins) { SpringLayout layout = (SpringLayout) container.getLayout(); Component[] components = container.getComponents(); Spring maxRightSpring = Spring.constant(0); Spring maxHeightSpring = Spring.constant(0); for (int i = 0; i < components.length; i++) { SpringLayout.Constraints cons = layout.getConstraints(components[i]); maxRightSpring = Spring.max(maxRightSpring, cons.getConstraint(SpringLayout.EAST)); } layout.getConstraints(container).setConstraint( SpringLayout.EAST, Spring.sum(Spring.constant(margins.getMargin(SpringLayout.EAST)), maxRightSpring)); for (int i = 0; i < components.length; i++) { SpringLayout.Constraints cons = layout.getConstraints(components[i]); maxHeightSpring = Spring.max(maxHeightSpring, cons.getConstraint(SpringLayout.SOUTH)); } layout.getConstraints(container).setConstraint( SpringLayout.SOUTH, Spring.sum(Spring.constant(margins.getMargin(SpringLayout.SOUTH)), maxHeightSpring)); } public void makeGrid(Container parent, int rows, int cols, int initialX, int initialY, int xPad, int yPad) { SpringLayout layout; try { layout = (SpringLayout)parent.getLayout(); } catch (ClassCastException exc) { System.err.println("The first argument to makeGrid must use SpringLayout."); return; } Spring xPadSpring = Spring.constant(xPad); Spring yPadSpring = Spring.constant(yPad); Spring initialXSpring = Spring.constant(initialX); Spring initialYSpring = Spring.constant(initialY); int max = rows * cols; //Calculate Springs that are the max of the width/height so that all //cells have the same size. Spring maxWidthSpring = layout.getConstraints(parent.getComponent(0)).getWidth(); Spring maxHeightSpring = layout.getConstraints(parent.getComponent(0)).getHeight(); for (int i = 1; i < max; i++) { SpringLayout.Constraints cons = layout.getConstraints( parent.getComponent(i)); maxWidthSpring = Spring.max(maxWidthSpring, cons.getWidth()); maxHeightSpring = Spring.max(maxHeightSpring, cons.getHeight()); } //Apply the new width/height Spring. This forces all the //components to have the same size. for (int i = 0; i < max; i++) { SpringLayout.Constraints cons = layout.getConstraints( parent.getComponent(i)); cons.setWidth(maxWidthSpring); cons.setHeight(maxHeightSpring); } //Then adjust the x/y constraints of all the cells so that they //are aligned in a grid. SpringLayout.Constraints lastCons = null; SpringLayout.Constraints lastRowCons = null; for (int i = 0; i < max; i++) { SpringLayout.Constraints cons = layout.getConstraints( parent.getComponent(i)); if (i % cols == 0) { //start of new row lastRowCons = lastCons; cons.setX(initialXSpring); } else { //x position depends on previous component cons.setX(Spring.sum(lastCons.getConstraint(SpringLayout.EAST), xPadSpring)); } if (i / cols == 0) { //first row cons.setY(initialYSpring); } else { //y position depends on previous row cons.setY(Spring.sum(lastRowCons.getConstraint(SpringLayout.SOUTH), yPadSpring)); } lastCons = cons; } //Set the parent's size. SpringLayout.Constraints pCons = layout.getConstraints(parent); pCons.setConstraint(SpringLayout.SOUTH, Spring.sum( Spring.constant(yPad), lastCons.getConstraint(SpringLayout.SOUTH))); pCons.setConstraint(SpringLayout.EAST, Spring.sum( Spring.constant(xPad), lastCons.getConstraint(SpringLayout.EAST))); } }