@Override public void visitElement(@NonNull XmlContext context, @NonNull Element element) { // Traverse all child elements NodeList childNodes = element.getChildNodes(); int count = childNodes.getLength(); Map<String, LayoutNode> nodes = Maps.newHashMap(); for (int i = 0; i < count; i++) { Node node = childNodes.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { LayoutNode ln = new LayoutNode((Element) node, i); nodes.put(ln.getNodeId(), ln); } } // Node map is populated, recalculate nodes sizes for (LayoutNode ln : nodes.values()) { ln.processNode(nodes); } for (LayoutNode right : nodes.values()) { if (!right.mLastLeft || right.skip()) { continue; } Set<LayoutNode> canGrowLeft = right.canGrowLeft(); for (LayoutNode left : nodes.values()) { if (left == right || !left.mLastRight || left.skip() || !left.sameBucket(right)) { continue; } Set<LayoutNode> canGrowRight = left.canGrowRight(); if (canGrowLeft.size() > 0 || canGrowRight.size() > 0) { canGrowRight.addAll(canGrowLeft); LayoutNode nodeToBlame = right; LayoutNode otherNode = left; if (!canGrowRight.contains(right) && canGrowRight.contains(left)) { nodeToBlame = left; otherNode = right; } context.report( ISSUE, nodeToBlame.getNode(), context.getLocation(nodeToBlame.getNode()), String.format( "`%1$s` can overlap `%2$s` if %3$s %4$s due to localized text expansion", nodeToBlame.getNodeId(), otherNode.getNodeId(), Joiner.on(", ").join(canGrowRight), canGrowRight.size() > 1 ? "grow" : "grows")); } } } }
/** * Process a node of a layout. Put it into one of three processing units and determine its right * and left neighbours. */ public void processNode(@NonNull Map<String, LayoutNode> nodes) { if (mProcessed) { return; } mProcessed = true; if (isInvisible() || hasAttr(ATTR_LAYOUT_ALIGN_RIGHT) || hasAttr(ATTR_LAYOUT_ALIGN_END) || hasAttr(ATTR_LAYOUT_ALIGN_LEFT) || hasAttr(ATTR_LAYOUT_ALIGN_START)) { mBucket = Bucket.SKIP; } else if (hasTrueAttr(ATTR_LAYOUT_ALIGN_PARENT_TOP)) { mBucket = Bucket.TOP; } else if (hasTrueAttr(ATTR_LAYOUT_ALIGN_PARENT_BOTTOM)) { mBucket = Bucket.BOTTOM; } else { if (hasAttr(ATTR_LAYOUT_ABOVE) || hasAttr(ATTR_LAYOUT_BELOW)) { mBucket = Bucket.SKIP; } else { String[] checkAlignment = { ATTR_LAYOUT_ALIGN_TOP, ATTR_LAYOUT_ALIGN_BOTTOM, ATTR_LAYOUT_ALIGN_BASELINE }; for (String alignment : checkAlignment) { String value = mNode.getAttributeNS(ANDROID_URI, alignment); if (!value.isEmpty()) { LayoutNode otherNode = nodes.get(uniformId(value)); if (otherNode != null) { otherNode.processNode(nodes); mBucket = otherNode.mBucket; } } } } } if (mBucket == null) { mBucket = Bucket.TOP; } // Check relative placement mToLeft = findNodeByAttr(nodes, ATTR_LAYOUT_TO_START_OF); if (mToLeft == null) { mToLeft = findNodeByAttr(nodes, ATTR_LAYOUT_TO_LEFT_OF); } if (mToLeft != null) { mToLeft.mLastLeft = false; mLastRight = false; } mToRight = findNodeByAttr(nodes, ATTR_LAYOUT_TO_END_OF); if (mToRight == null) { mToRight = findNodeByAttr(nodes, ATTR_LAYOUT_TO_RIGHT_OF); } if (mToRight != null) { mToRight.mLastLeft = false; mLastRight = false; } if (hasTrueAttr(ATTR_LAYOUT_ALIGN_PARENT_END) || hasTrueAttr(ATTR_LAYOUT_ALIGN_PARENT_RIGHT)) { mLastRight = false; } if (hasTrueAttr(ATTR_LAYOUT_ALIGN_PARENT_START) || hasTrueAttr(ATTR_LAYOUT_ALIGN_PARENT_LEFT)) { mLastLeft = false; } if (mToLeft == null && mToRight == null && mLastRight && mLastLeft) { mLastLeft = false; } }