OSDN Git Service

Capture views with IDs and no expressions in Binding.
authorGeorge Mount <mount@google.com>
Tue, 31 Mar 2015 23:26:13 +0000 (16:26 -0700)
committerGeorge Mount <mount@google.com>
Thu, 2 Apr 2015 17:23:37 +0000 (10:23 -0700)
We want to get all Views with IDs in the Binding to save the
developer effort in calling findViewById.

Change-Id: Ib7dd85ae9ecc0fd31b235364c0eadc2303dd1780

tools/data-binding/compiler/src/main/java/android/databinding/tool/store/LayoutFileParser.java
tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt
tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/NoIdTest.java
tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/no_id_test.xml
tools/data-binding/library/src/main/java/android/databinding/ViewDataBinding.java

index 8889b94..6a35bd2 100644 (file)
@@ -49,9 +49,10 @@ import javax.xml.xpath.XPathFactory;
  */
 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)
@@ -130,7 +131,7 @@ public class LayoutFileParser {
                         ParserHelper.INSTANCE$.toClassName(layoutName) + "Binding";
                 includedLayoutName = layoutName;
             } else {
-                className = getFullViewClassName(nodeName);
+                className = getFullViewClassName(parent);
             }
             final Node originalTag = attributes.getNamedItem("android:tag");
             final String tag;
@@ -156,9 +157,28 @@ public class LayoutFileParser {
             }
         }
 
+        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);
     }
@@ -171,6 +191,10 @@ public class LayoutFileParser {
         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);
@@ -185,7 +209,16 @@ public class LayoutFileParser {
         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")) {
index 56d4394..228e759 100644 (file)
@@ -395,11 +395,11 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
     }
     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);
         }
     }
@@ -416,13 +416,16 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
                     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)});")
                 }
             }
index 52b7cb8..f86ac08 100644 (file)
@@ -83,4 +83,9 @@ public class NoIdTest extends BaseDataBinderTest<NoIdTestBinding> {
         String expectedValue = view.getResources().getString(android.R.string.ok);
         assertEquals(expectedValue, view.getTag());
     }
+
+    @UiThreadTest
+    public void testIdOnly() {
+        assertEquals("hello", mBinder.textView.getText().toString());
+    }
 }
index 61cc652..32f06f4 100644 (file)
@@ -14,4 +14,8 @@
               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>
index d8247c5..4e93469 100644 (file)
@@ -19,7 +19,6 @@ package android.databinding;
 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;
@@ -296,18 +295,11 @@ public abstract class ViewDataBinding {
             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);
@@ -330,10 +322,29 @@ public abstract class ViewDataBinding {
     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.