diff options
author | George Mount <mount@google.com> | 2015-03-27 16:05:21 -0700 |
---|---|---|
committer | George Mount <mount@google.com> | 2015-03-27 16:05:21 -0700 |
commit | 46bb16c3034c12b347d08273e80adaed30f08104 (patch) | |
tree | ddb36c8edd76fb5d08c16575a4b810f03e1da23a /tools | |
parent | efff1c249ea36e3c41e896c07cb00fc1db04672a (diff) | |
download | frameworks_base-46bb16c3034c12b347d08273e80adaed30f08104.zip frameworks_base-46bb16c3034c12b347d08273e80adaed30f08104.tar.gz frameworks_base-46bb16c3034c12b347d08273e80adaed30f08104.tar.bz2 |
Make ViewStub support binding variables like include.
Bug 19969378
Diffstat (limited to 'tools')
12 files changed, 397 insertions, 40 deletions
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/Binding.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/Binding.java index 247735d..f2bc96f 100644 --- a/tools/data-binding/compiler/src/main/java/android/databinding/tool/Binding.java +++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/Binding.java @@ -20,6 +20,7 @@ import android.databinding.tool.expr.Expr; import android.databinding.tool.reflection.ModelAnalyzer; import android.databinding.tool.reflection.ModelClass; import android.databinding.tool.store.SetterStore; +import android.databinding.tool.store.SetterStore.SetterCall; public class Binding { @@ -37,8 +38,16 @@ public class Binding { private SetterStore.SetterCall getSetterCall() { if (mSetterCall == null) { ModelClass viewType = mTarget.getResolvedType(); - mSetterCall = SetterStore.get(ModelAnalyzer.getInstance()).getSetterCall(mName, - viewType, mExpr.getResolvedType(), mExpr.getModel().getImports()); + if (viewType != null && viewType.extendsViewStub()) { + if (isViewStubAttribute()) { + mSetterCall = new ViewStubDirectCall(mName, viewType, mExpr); + } else { + mSetterCall = new ViewStubSetterCall(mName); + } + } else { + mSetterCall = SetterStore.get(ModelAnalyzer.getInstance()).getSetterCall(mName, + viewType, mExpr.getResolvedType(), mExpr.getModel().getImports()); + } } return mSetterCall; } @@ -77,4 +86,55 @@ public class Binding { public Expr getExpr() { return mExpr; } + + private boolean isViewStubAttribute() { + if ("android:inflatedId".equals(mName)) { + return true; + } else if ("android:layout".equals(mName)) { + return true; + } else if ("android:visibility".equals(mName)) { + return true; + } else { + return false; + } + } + + private static class ViewStubSetterCall extends SetterCall { + private final String mName; + + public ViewStubSetterCall(String name) { + mName = name.substring(name.lastIndexOf(':') + 1); + } + + @Override + protected String toJavaInternal(String viewExpression, String converted) { + return "if (" + viewExpression + ".isInflated()) " + viewExpression + + ".getBinding().setVariable(BR." + mName + ", " + converted + ")"; + } + + @Override + public int getMinApi() { + return 0; + } + } + + private static class ViewStubDirectCall extends SetterCall { + private final SetterCall mWrappedCall; + + public ViewStubDirectCall(String name, ModelClass viewType, Expr expr) { + mWrappedCall = SetterStore.get(ModelAnalyzer.getInstance()).getSetterCall(name, + viewType, expr.getResolvedType(), expr.getModel().getImports()); + } + + @Override + protected String toJavaInternal(String viewExpression, String converted) { + return "if (!" + viewExpression + ".isInflated()) " + + mWrappedCall.toJava(viewExpression + ".getViewStub()", converted); + } + + @Override + public int getMinApi() { + return 0; + } + } } diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java index 318eaa0..187b9dc 100644 --- a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java +++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java @@ -68,6 +68,8 @@ public abstract class ModelAnalyzer { public static final String VIEW_DATA_BINDING = "android.databinding.ViewDataBinding"; + public static final String VIEW_STUB_CLASS_NAME = "android.view.ViewStub"; + private ModelClass[] mListTypes; private ModelClass mMapType; private ModelClass mStringType; @@ -77,6 +79,7 @@ public abstract class ModelAnalyzer { private ModelClass mObservableMapType; private ModelClass[] mObservableFieldTypes; private ModelClass mViewBindingType; + private ModelClass mViewStubType; private static ModelAnalyzer sAnalyzer; @@ -282,6 +285,13 @@ public abstract class ModelAnalyzer { return mObservableFieldTypes; } + ModelClass getViewStubType() { + if (mViewStubType == null) { + mViewStubType = findClass(VIEW_STUB_CLASS_NAME, null); + } + return mViewStubType; + } + private ModelClass loadClassErasure(String className) { return findClass(className, null).erasure(); } diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java index 5b05a3e..c55a400 100644 --- a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java +++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java @@ -129,6 +129,13 @@ public abstract class ModelClass { } /** + * @return whether or not this ModelClass type extends ViewStub. + */ + public boolean extendsViewStub() { + return ModelAnalyzer.getInstance().getViewStubType().isAssignableFrom(this); + } + + /** * @return whether or not this is an Observable type such as ObservableMap, ObservableList, * or Observable. */ diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/store/ResourceBundle.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/store/ResourceBundle.java index 8468efc..c115387 100644 --- a/tools/data-binding/compiler/src/main/java/android/databinding/tool/store/ResourceBundle.java +++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/store/ResourceBundle.java @@ -17,6 +17,8 @@ import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; +import android.databinding.tool.reflection.ModelAnalyzer; +import android.databinding.tool.reflection.ModelClass; import android.databinding.tool.util.L; import android.databinding.tool.util.ParserHelper; diff --git a/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt index bba5cd9..1ecdf67 100644 --- a/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt +++ b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt @@ -66,7 +66,7 @@ class ExprModelExt { } } -val ExprModel.ext by Delegates.lazy { (target : ExprModel) -> +val ExprModel.ext by Delegates.lazy { target : ExprModel -> ExprModelExt() } @@ -74,7 +74,7 @@ fun ExprModel.getUniqueFieldName(base : String) : String = ext.getUniqueFieldNam fun ExprModel.localizeFlag(set : FlagSet, base : String) : FlagSet = ext.localizeFlag(set, base) -val BindingTarget.readableUniqueName by Delegates.lazy {(target: BindingTarget) -> +val BindingTarget.readableUniqueName by Delegates.lazy { target: BindingTarget -> val variableName : String if (target.getId() == null) { variableName = "boundView" + target.getTag() @@ -84,7 +84,17 @@ val BindingTarget.readableUniqueName by Delegates.lazy {(target: BindingTarget) target.getModel().ext.getUniqueFieldName(variableName) } -val BindingTarget.fieldName by Delegates.lazy { (target : BindingTarget) -> +fun BindingTarget.superConversion(variable : String) : String { + if (isBinder()) { + return "${getViewClass()}.bind(${variable})" + } else if (getResolvedType() != null && getResolvedType().extendsViewStub()) { + return "new android.databinding.ViewStubProxy((android.view.ViewStub) ${variable})" + } else { + return "(${interfaceType}) ${variable}" + } +} + +val BindingTarget.fieldName by Delegates.lazy { target : BindingTarget -> if (target.getFieldName() == null) { if (target.getId() == null) { target.setFieldName("m${target.readableUniqueName.capitalize()}") @@ -96,61 +106,70 @@ val BindingTarget.fieldName by Delegates.lazy { (target : BindingTarget) -> target.getFieldName(); } -val BindingTarget.getterName by Delegates.lazy { (target : BindingTarget) -> +val BindingTarget.getterName by Delegates.lazy { target : BindingTarget -> "get${target.readableUniqueName.capitalize()}" } -val BindingTarget.androidId by Delegates.lazy { (target : BindingTarget) -> +val BindingTarget.androidId by Delegates.lazy { target : BindingTarget -> "R.id.${target.getId().androidId()}" } -val Expr.readableUniqueName by Delegates.lazy { (expr : Expr) -> +val BindingTarget.interfaceType by Delegates.lazy { target : BindingTarget -> + if (target.getResolvedType() != null && target.getResolvedType().extendsViewStub()) { + "android.databinding.ViewStubProxy" + } else { + target.getInterfaceType() + } +} + +val Expr.readableUniqueName by Delegates.lazy { expr : Expr -> Log.d { "readableUniqueName for ${expr.getUniqueKey()}" } val stripped = "${expr.getUniqueKey().stripNonJava()}" expr.getModel().ext.getUniqueFieldName(stripped) } -val Expr.fieldName by Delegates.lazy { (expr : Expr) -> - "m${expr.readableUniqueName.capitalize()}" +val Expr.readableName by Delegates.lazy { expr : Expr -> + Log.d { "readableUniqueName for ${expr.getUniqueKey()}" } + "${expr.getUniqueKey().stripNonJava()}" +} + +val Expr.fieldName by Delegates.lazy { expr : Expr -> + "m${expr.readableName.capitalize()}" } -val Expr.hasFlag by Delegates.lazy { (expr : Expr) -> +val Expr.hasFlag by Delegates.lazy { expr : Expr -> expr.getId() < expr.getModel().getInvalidateableFieldLimit() } -val Expr.localName by Delegates.lazy { (expr : Expr) -> +val Expr.localName by Delegates.lazy { expr : Expr -> if(expr.isVariable()) expr.fieldName else "${expr.readableUniqueName}" } -val Expr.setterName by Delegates.lazy { (expr : Expr) -> - "set${expr.readableUniqueName.capitalize()}" +val Expr.setterName by Delegates.lazy { expr : Expr -> + "set${expr.readableName.capitalize()}" } -val Expr.onChangeName by Delegates.lazy { (expr : Expr) -> +val Expr.onChangeName by Delegates.lazy { expr : Expr -> "onChange${expr.readableUniqueName.capitalize()}" } -val Expr.getterName by Delegates.lazy { (expr : Expr) -> - "get${expr.readableUniqueName.capitalize()}" +val Expr.getterName by Delegates.lazy { expr : Expr -> + "get${expr.readableName.capitalize()}" } -val Expr.staticFieldName by Delegates.lazy { (expr : Expr) -> - "s${expr.readableUniqueName.capitalize()}" -} - -val Expr.dirtyFlagName by Delegates.lazy { (expr : Expr) -> +val Expr.dirtyFlagName by Delegates.lazy { expr : Expr -> "sFlag${expr.readableUniqueName.capitalize()}" } -val Expr.shouldReadFlagName by Delegates.lazy { (expr : Expr) -> +val Expr.shouldReadFlagName by Delegates.lazy { expr : Expr -> "sFlagRead${expr.readableUniqueName.capitalize()}" } -val Expr.invalidateFlagName by Delegates.lazy { (expr : Expr) -> +val Expr.invalidateFlagName by Delegates.lazy { expr : Expr -> "sFlag${expr.readableUniqueName.capitalize()}Invalid" } -val Expr.conditionalFlagPrefix by Delegates.lazy { (expr : Expr) -> +val Expr.conditionalFlagPrefix by Delegates.lazy { expr : Expr -> "sFlag${expr.readableUniqueName.capitalize()}Is" } @@ -238,22 +257,22 @@ fun Expr.isVariable() = this is IdentifierExpr && this.isDynamic() fun Expr.conditionalFlagName(output : Boolean, suffix : String) = "${dirtyFlagName}_${output}$suffix" -val Expr.dirtyFlagSet by Delegates.lazy { (expr : Expr) -> +val Expr.dirtyFlagSet by Delegates.lazy { expr : Expr -> val fs = FlagSet(expr.getInvalidFlags(), expr.getModel().getFlagBucketCount()) expr.getModel().localizeFlag(fs, expr.dirtyFlagName) } -val Expr.invalidateFlagSet by Delegates.lazy { (expr : Expr) -> +val Expr.invalidateFlagSet by Delegates.lazy { expr : Expr -> val fs = FlagSet(expr.getId()) expr.getModel().localizeFlag(fs, expr.invalidateFlagName) } -val Expr.shouldReadFlagSet by Delegates.lazy { (expr : Expr) -> +val Expr.shouldReadFlagSet by Delegates.lazy { expr : Expr -> val fs = FlagSet(expr.getShouldReadFlags(), expr.getModel().getFlagBucketCount()) expr.getModel().localizeFlag(fs, expr.shouldReadFlagName) } -val Expr.conditionalFlags by Delegates.lazy { (expr : Expr) -> +val Expr.conditionalFlags by Delegates.lazy { expr : Expr -> val model = expr.getModel() arrayListOf(model.localizeFlag(FlagSet(expr.getRequirementFlagIndex(false)), "${expr.conditionalFlagPrefix}False"), @@ -408,18 +427,23 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) { val index = indices.get(it) if (!it.isUsed()) { tab(", null") - } else if (index == null) { - tab(", (${it.getInterfaceType()}) root") - } else if (it.isBinder()) { - tab(", ${it.getViewClass()}.bind(views[${index}])") - } else { - tab(", (${it.getInterfaceType()}) views[${index}]") + } else{ + val variableName : String + if (index == null) { + variableName = "root"; + } else { + variableName = "views[${index}]" + } + tab(", ${it.superConversion(variableName)}") } } tab(");") } val taggedViews = layoutBinder.getBindingTargets().filter{it.isUsed() && !it.isBinder()} taggedViews.forEach { + if (it.getResolvedType() != null && it.getResolvedType().extendsViewStub()) { + tab("this.${it.fieldName}.setContainingBinding(this);") + } if (it.getTag() == null) { if (it.getId() == null) { tab("this.${it.fieldName} = (${it.getViewClass()}) root;") @@ -584,7 +608,7 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) { fun declareViews() = kcode("// views") { layoutBinder.getBindingTargets().filter {it.isUsed() && (it.getId() == null)}.forEach { - nl("private final ${it.getInterfaceType()} ${it.fieldName};") + nl("private final ${it.interfaceType} ${it.fieldName};") } } @@ -596,7 +620,7 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) { fun declareDirtyFlags() = kcode("// dirty flag") { model.ext.localizedFlags.forEach { flag -> - flag.notEmpty { (suffix, value) -> + flag.notEmpty { suffix, value -> nl("private") app(" ", if(flag.isDynamic()) null else "static final"); app(" ", " ${flag.type} ${flag.getLocalName()}$suffix = $value;") @@ -682,6 +706,14 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) { includedBinders.filter{it.isUsed()}.forEach { binder -> tab("${binder.fieldName}.executePendingBindings();") } + layoutBinder.getBindingTargets().filter{ + it.isUsed() && it.getResolvedType() != null && it.getResolvedType().extendsViewStub() + }.forEach { + tab("if (${it.fieldName}.getBinding() != null) {") { + tab("${it.fieldName}.getBinding().executePendingBindings();") + } + tab("}") + } } nl("}") } @@ -760,12 +792,12 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) { nl("import android.databinding.ViewDataBinding;") nl("public abstract class ${baseClassName} extends ViewDataBinding {") layoutBinder.getBindingTargets().filter{it.getId() != null}.forEach { - tab("public final ${it.getInterfaceType()} ${it.fieldName};") + tab("public final ${it.interfaceType} ${it.fieldName};") } nl("") tab("protected ${baseClassName}(android.view.View root_, int localFieldCount") { layoutBinder.getBindingTargets().filter{it.getId() != null}.forEach { - tab(", ${it.getInterfaceType()} ${it.readableUniqueName}") + tab(", ${it.interfaceType} ${it.readableUniqueName}") } } tab(") {") { diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubBindingAdapterTest.java index 57bbb5e..2ebe2b0 100644 --- a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubBindingAdapterTest.java +++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubBindingAdapterTest.java @@ -33,7 +33,7 @@ public class ViewStubBindingAdapterTest @Override protected void setUp() throws Exception { super.setUp(); - mView = mBinder.view; + mView = mBinder.view.getViewStub(); } public void testLayout() throws Throwable { diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubTest.java new file mode 100644 index 0000000..a63e5df --- /dev/null +++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.databinding.testapp; + +import android.databinding.testapp.generated.ViewStubBinding; +import android.databinding.testapp.generated.ViewStubContentsBinding; +import android.databinding.ViewStubProxy; +import android.support.v4.util.ArrayMap; +import android.test.UiThreadTest; +import android.view.View; +import android.widget.TextView; + +import java.util.ArrayList; + +public class ViewStubTest extends BaseDataBinderTest<ViewStubBinding> { + + public ViewStubTest() { + super(ViewStubBinding.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + mBinder.setViewStubVisibility(View.GONE); + mBinder.setFirstName("Hello"); + mBinder.setLastName("World"); + try { + runTestOnUiThread(new Runnable() { + @Override + public void run() { + mBinder.executePendingBindings(); + } + }); + } catch (Exception e) { + throw e; + } catch (Throwable t) { + throw new Exception(t); + } + } + + @UiThreadTest + public void testInflation() throws Throwable { + ViewStubProxy viewStubProxy = mBinder.viewStub; + assertFalse(viewStubProxy.isInflated()); + assertNull(viewStubProxy.getBinding()); + assertNotNull(viewStubProxy.getViewStub()); + assertNull(mBinder.getRoot().findViewById(R.id.firstNameContents)); + assertNull(mBinder.getRoot().findViewById(R.id.lastNameContents)); + mBinder.setViewStubVisibility(View.VISIBLE); + mBinder.executePendingBindings(); + assertTrue(viewStubProxy.isInflated()); + assertNotNull(viewStubProxy.getBinding()); + assertNull(viewStubProxy.getViewStub()); + ViewStubContentsBinding contentsBinding = (ViewStubContentsBinding) + viewStubProxy.getBinding(); + assertNotNull(contentsBinding.firstNameContents); + assertNotNull(contentsBinding.lastNameContents); + assertEquals("Hello", contentsBinding.firstNameContents.getText().toString()); + assertEquals("World", contentsBinding.lastNameContents.getText().toString()); + } + + @UiThreadTest + public void testChangeValues() throws Throwable { + ViewStubProxy viewStubProxy = mBinder.viewStub; + mBinder.setViewStubVisibility(View.VISIBLE); + mBinder.executePendingBindings(); + ViewStubContentsBinding contentsBinding = (ViewStubContentsBinding) + viewStubProxy.getBinding(); + assertEquals("Hello", contentsBinding.firstNameContents.getText().toString()); + mBinder.setFirstName("Goodbye"); + mBinder.executePendingBindings(); + assertEquals("Goodbye", contentsBinding.firstNameContents.getText().toString()); + } +} diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/layout_with_include.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/layout_with_include.xml index 5f5da5a..6504e4c 100644 --- a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/layout_with_include.xml +++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/layout_with_include.xml @@ -26,4 +26,5 @@ bind:innerObject="@{outerObject}" bind:innerValue="@{`modified ` + outerObject.intValue}" /> + <include layout="@layout/plain_layout" android:id="@+id/plainLayout"/> </LinearLayout> diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/plain_layout.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/plain_layout.xml new file mode 100644 index 0000000..2132370 --- /dev/null +++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/plain_layout.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"/> diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub.xml new file mode 100644 index 0000000..af0796e --- /dev/null +++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + xmlns:bind="http://schemas.android.com/apk/res-auto"> + <variable name="viewStubVisibility" type="int"/> + <variable name="firstName" type="String"/> + <variable name="lastName" type="String"/> + <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" + android:text="@{firstName}" + android:id="@+id/firstName" + /> + <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" + android:text="@{lastName}" + android:id="@+id/lastName" + /> + + <ViewStub android:layout_width="match_parent" android:layout_height="match_parent" + android:id="@+id/viewStub" + android:visibility="@{viewStubVisibility}" + android:layout="@layout/view_stub_contents" + bind:firstName="@{firstName}" + bind:lastName="@{lastName}"/> +</LinearLayout> diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub_contents.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub_contents.xml new file mode 100644 index 0000000..9305189 --- /dev/null +++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub_contents.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <variable name="firstName" type="String"/> + <variable name="lastName" type="String"/> + <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" + android:text="@{firstName}" + android:id="@+id/firstNameContents"/> + <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" + android:text="@{lastName}" + android:id="@+id/lastNameContents"/> +</LinearLayout> diff --git a/tools/data-binding/library/src/main/java/android/databinding/ViewStubProxy.java b/tools/data-binding/library/src/main/java/android/databinding/ViewStubProxy.java new file mode 100644 index 0000000..01b878a --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/databinding/ViewStubProxy.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.databinding; + +import android.view.View; +import android.view.ViewStub; +import android.view.ViewStub.OnInflateListener; + +/** + * This class represents a ViewStub before and after inflation. Before inflation, + * the ViewStub is accessible. After inflation, the ViewDataBinding is accessible + * if the inflated View has bindings. If not, the root View will be accessible. + */ +public class ViewStubProxy { + private ViewStub mViewStub; + private ViewDataBinding mViewDataBinding; + private View mRoot; + private OnInflateListener mOnInflateListener; + private ViewDataBinding mContainingBinding; + + private OnInflateListener mProxyListener = new OnInflateListener() { + @Override + public void onInflate(ViewStub stub, View inflated) { + mRoot = inflated; + mViewDataBinding = DataBindingUtil.bindTo(inflated, stub.getLayoutResource()); + mViewStub = null; + + if (mOnInflateListener != null) { + mOnInflateListener.onInflate(stub, inflated); + mOnInflateListener = null; + } + mContainingBinding.invalidateAll(); + mContainingBinding.executePendingBindings(); + } + }; + + public ViewStubProxy(ViewStub viewStub) { + mViewStub = viewStub; + mViewStub.setOnInflateListener(mProxyListener); + } + + public void setContainingBinding(ViewDataBinding containingBinding) { + mContainingBinding = containingBinding; + } + + /** + * @return <code>true</code> if the ViewStub has replaced itself with the inflated layout + * or <code>false</code> if not. + */ + public boolean isInflated() { + return mRoot != null; + } + + /** + * @return The root View of the layout replacing the ViewStub once it has been inflated. + * <code>null</code> is returned prior to inflation. + */ + public View getRoot() { + return mRoot; + } + + /** + * @return The data binding associated with the inflated layout once it has been inflated. + * <code>null</code> prior to inflation or if there is no binding associated with the layout. + */ + public ViewDataBinding getBinding() { + return mViewDataBinding; + } + + /** + * @return The ViewStub in the layout or <code>null</code> if the ViewStub has been inflated. + */ + public ViewStub getViewStub() { + return mViewStub; + } + + /** + * Sets the {@link OnInflateListener} to be called when the ViewStub inflates. The proxy must + * have an OnInflateListener, so <code>listener</code> will be called immediately after + * the proxy's listener is called. + * + * @param listener The OnInflateListener to notify of successful inflation + */ + public void setOnInflateListener(OnInflateListener listener) { + if (mViewStub != null) { + mOnInflateListener = listener; + } + } +} |