*/
public class LayoutFileParser {
private static final String XPATH_VARIABLE_DEFINITIONS = "//variable";
- private static final String XPATH_BINDING_2_EXPR = "//@*[starts-with(., '@{') and substring(., string-length(.)) = '}']";
- private static final String XPATH_BINDING_ELEMENTS = XPATH_BINDING_2_EXPR + "/..";
+ private static final String XPATH_BINDING_ELEMENTS = "//*[@*[starts-with(., '@{') and substring(., string-length(.)) = '}']]";
+ private static final String XPATH_ID_ELEMENTS = "//*[@*[local-name()='id']]";
private static final String XPATH_IMPORT_DEFINITIONS = "//import";
+ private static final String XPATH_MERGE_TAG = "/merge";
final String LAYOUT_PREFIX = "@layout/";
public ResourceBundle.LayoutFileBundle parseXml(File xml, String pkg, int layoutId)
ParserHelper.INSTANCE$.toClassName(layoutName) + "Binding";
includedLayoutName = layoutName;
} else {
- className = getFullViewClassName(nodeName);
+ className = getFullViewClassName(parent);
}
final Node originalTag = attributes.getNamedItem("android:tag");
final String tag;
}
}
+ if (!bindingNodes.isEmpty() || !imports.isEmpty() || !variableNodes.isEmpty()) {
+ if (isMergeLayout(doc, xPath)) {
+ L.e("<merge> is not allowed with data binding.");
+ throw new RuntimeException("<merge> is not allowed with data binding.");
+ }
+ final List<Node> idNodes = getNakedIds(doc, xPath);
+ for (Node node : idNodes) {
+ if (!bindingNodes.contains(node) && !"include".equals(node.getNodeName())) {
+ final Node id = node.getAttributes().getNamedItem("android:id");
+ final String className = getFullViewClassName(node);
+ bundle.createBindingTarget(id.getNodeValue(), className, true, null, null);
+ }
+ }
+ }
+
return bundle;
}
+ private boolean isMergeLayout(Document doc, XPath xPath) throws XPathExpressionException {
+ return !get(doc, xPath, XPATH_MERGE_TAG).isEmpty();
+ }
+
private List<Node> getBindingNodes(Document doc, XPath xPath) throws XPathExpressionException {
return get(doc, xPath, XPATH_BINDING_ELEMENTS);
}
return get(doc, xPath, XPATH_IMPORT_DEFINITIONS);
}
+ private List<Node> getNakedIds(Document doc, XPath xPath) throws XPathExpressionException {
+ return get(doc, xPath, XPATH_ID_ELEMENTS);
+ }
+
private List<Node> get(Document doc, XPath xPath, String pattern)
throws XPathExpressionException {
final XPathExpression expr = xPath.compile(pattern);
return result;
}
- private String getFullViewClassName(String viewName) {
+ private String getFullViewClassName(Node viewNode) {
+ String viewName = viewNode.getNodeName();
+ if ("view".equals(viewName)) {
+ Node classNode = viewNode.getAttributes().getNamedItem("class");
+ if (classNode == null) {
+ L.e("No class attribute for 'view' node");
+ } else {
+ viewName = classNode.getNodeValue();
+ }
+ }
if (viewName.indexOf('.') == -1) {
if (ObjectUtils.equals(viewName, "View") || ObjectUtils.equals(viewName, "ViewGroup") ||
ObjectUtils.equals(viewName, "ViewStub")) {
}
fun calculateIndices() : Unit {
val numTaggedViews = layoutBinder.getBindingTargets().
- filter{it.isUsed() && !it.isBinder()}.count()
+ filter{it.isUsed() && it.getTag() != null}.count()
layoutBinder.getBindingTargets().filter{ it.isUsed() && it.getTag() != null }.forEach {
indices.put(it, Integer.parseInt(it.getTag()));
}
- layoutBinder.getBindingTargets().filter{ it.isUsed() && it.isBinder()}.withIndex().forEach {
+ layoutBinder.getBindingTargets().filter{ it.isUsed() && it.getTag() == null && it.getId() != null }.withIndex().forEach {
indices.put(it.value, it.index + numTaggedViews);
}
}
tab("sIncludes.put(${it.androidId}, ${indices.get(it)});")
}
}
- val hasViewsWithIds = layoutBinder.getBindingTargets().firstOrNull{ it.isUsed() && !it.supportsTag()} != null
+ val hasViewsWithIds = layoutBinder.getBindingTargets().firstOrNull{
+ it.isUsed() && (!it.supportsTag() || (it.getId() != null && it.getTag() == null))
+ } != null
if (!hasViewsWithIds) {
tab("sViewsWithIds = null;")
} else {
tab("sViewsWithIds = new android.util.SparseIntArray();")
- layoutBinder.getBindingTargets().filter{ it.isUsed() && !it.supportsTag() }.
- forEach {
+ layoutBinder.getBindingTargets().filter{
+ it.isUsed() && (!it.supportsTag() || (it.getId() != null && it.getTag() == null))
+ }.forEach {
tab("sViewsWithIds.put(${it.androidId}, ${indices.get(it)});")
}
}
String expectedValue = view.getResources().getString(android.R.string.ok);
assertEquals(expectedValue, view.getTag());
}
+
+ @UiThreadTest
+ public void testIdOnly() {
+ assertEquals("hello", mBinder.textView.getText().toString());
+ }
}
android:text="@{name}" android:tag="@string/app_name"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="@{name}" android:tag="@android:string/ok"/>
+ <TextView android:id="@+id/textView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="hello"/>
</LinearLayout>
import android.annotation.TargetApi;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
-import android.util.Log;
import android.util.SparseIntArray;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
SparseIntArray viewsWithIds) {
boolean visitChildren = true;
String tag = (String) view.getTag();
- int id;
if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
int tagIndex = parseTagInt(tag);
views[tagIndex] = view;
- } else if ((id = view.getId()) > 0) {
- int index;
- if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0) {
- views[index] = view;
- } else if (includes != null && (index = includes.get(id, -1)) >= 0) {
- views[index] = view;
- visitChildren = false;
- }
+ } else {
+ visitChildren = addViewWithId(view, views, includes, viewsWithIds);
}
if (visitChildren) {
mapTaggedChildViews(view, views, includes, viewsWithIds);
protected static View[] mapChildViews(View root, int numViews, SparseIntArray includes,
SparseIntArray viewsWithIds) {
View[] views = new View[numViews];
- mapTaggedChildViews(root, views, includes, viewsWithIds);
+ boolean visitChildren = addViewWithId(root, views, includes, viewsWithIds);
+ if (visitChildren) {
+ mapTaggedChildViews(root, views, includes, viewsWithIds);
+ }
return views;
}
+ private static boolean addViewWithId(View view, View[] views, SparseIntArray includes,
+ SparseIntArray viewsWithIds) {
+ final int id = view.getId();
+ boolean visitChildren = true;
+ if (id > 0) {
+ int index;
+ if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0) {
+ views[index] = view;
+ } else if (includes != null && (index = includes.get(id, -1)) >= 0) {
+ views[index] = view;
+ visitChildren = false;
+ }
+ }
+ return visitChildren;
+ }
+
/**
* Parse the tag without creating a new String object. This is fast and assumes the
* tag is in the correct format.