Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4cd742ff authored by George Mount's avatar George Mount
Browse files

Capture views with IDs and no expressions in Binding.

We want to get all Views with IDs in the Binding to save the
developer effort in calling findViewById.

Change-Id: Ib7dd85ae9ecc0fd31b235364c0eadc2303dd1780
parent fcbf46e7
Loading
Loading
Loading
Loading
+37 −4
Original line number Diff line number Diff line
@@ -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")) {
+8 −5
Original line number Diff line number Diff line
@@ -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)});")
                }
            }
+5 −0
Original line number Diff line number Diff line
@@ -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());
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -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>
+22 −11
Original line number Diff line number Diff line
@@ -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];
        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.