John Murphy
John Murphy

Reputation: 965

Android Data Binding layout_width and layout_height

I need to be able to dynamically set an EditText's height property. I am using data binding for other properties throughout my app, so I would love to be able to use data binding to control the height of my elements. Here is a stripped down version of my xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<data>
    <variable name="loginVM" type="com.testing.stuff.ViewModels.LoginViewModel" />
</data>

<EditText android:inputType="number"
            android:id="@+id/txtVerificationCode"
            android:layout_height="@{loginVM.compact ? @dimen/verificationHeightCompact : @dimen/verificationHeightFull}"
            android:layout_width="match_parent"
            android:paddingRight="16dp"
            android:paddingLeft="16dp"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:layout_marginLeft="10dp"
            android:alpha="@{loginVM.verificationOpacity}"
            android:layout_marginStart="10dp"
            android:textAlignment="center"
            android:visibility="visible"
            android:hint="Enter verificationCode"
            android:text="@{loginVM.verificationCode}" />
</layout> 

And here is a stripped down version of my View Model:

public class LoginViewModel extends BaseObservable {
public final ObservableField<String> verificationCode; 
public final ObservableField<Boolean> compact;

@Bindable
public String getVerificationCode() {
    if (this.verificationCode == null) {
        return "";
    } else {
        return this.verificationCode.get();
    }
}

public void setVerificationCode(String verificationCode) {
    this.verificationCode.set(verificationCode);
    invalidateProperties();
}

@Bindable
public Boolean getCompact(){return this.compact.get();}

public void setCompact(Boolean value)
{
    this.compact.set(value);
    this.invalidateProperties();
}

@BindingAdapter("android:layout_height")
public static void setLayoutHeight(EditText view, float height)
{
    ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
    layoutParams.height = (int)height;
    view.setLayoutParams(layoutParams);
}

public LoginViewModel(Context ctx) {
    verificationCode = new ObservableField();
    compact = new ObservableField();
}

The dimensions are in the dimens.xml file. And I am modifying the properties in the view model. But, when I launch the app, I'm getting the following error immediately after launch (the bindingadapter is not firing on debug). I have several other elements on the screen but this particular one is the one I need to change the height when a particular action occurs:

FATAL EXCEPTION: main

Process: com.testing.stuff, PID: 32752

java.lang.RuntimeException: Unable to start activity  
ComponentInfo{com.testing.stuff/com.testing.stuff.Login}: 
java.lang.RuntimeException: Binary XML file line #69: You must supply 
a layout_height attribute.

Caused by: java.lang.RuntimeException: Binary XML file line #69: You 
must supply a layout_height attribute.

There are a few posts on SO regarding this issue but no definitive answers or the approach did not work. Surely this is an implementation that is common. Thanks in advance for the help.

Upvotes: 38

Views: 38720

Answers (7)

straya
straya

Reputation: 5059

This is a neat, scalable mechanism for switching between two layout_height or layout_width states using a boolean. You would need to import the LayoutWidthHeightBindingAdapterVariants class into layout to specify one of its values in the layout:

@BindingAdapter(value = ["android:layout_height", "app:layout_widthHeightVariant"])
fun expandContractHeight(view: View, flag: Boolean, variant: LayoutWidthHeightBindingAdapterVariants) {
    val params = view.layoutParams
    params.height = if (flag) variant.valueWhenTrue else variant.valueWhenFalse
    view.layoutParams = params
    view.invalidate()
}

@BindingAdapter(value = ["android:layout_width", "app:layout_widthHeightVariant"])
fun expandContractWidth(view: View, flag: Boolean, variant: LayoutWidthHeightBindingAdapterVariants) {
    val params = view.layoutParams
    params.height = if (flag) variant.valueWhenTrue else variant.valueWhenFalse
    view.layoutParams = params
    view.invalidate()
}

enum class LayoutWidthHeightBindingAdapterVariants(val valueWhenFalse: Int, val valueWhenTrue: Int) {
    // format: <falseValue>_TO_<trueValue>
    WRAP_TO_MATCH(WRAP_CONTENT, MATCH_PARENT),
    MATCH_TO_WRAP(MATCH_PARENT, WRAP_CONTENT),
    WRAP_TO_ZERODP(WRAP_CONTENT, 0),
    ZERODP_TO_WRAP(0, WRAP_CONTENT)
}

Upvotes: 1

Andreas Rudolph
Andreas Rudolph

Reputation: 1246

I solved it like this in Kotlin instead of Java.

Make a file called

MyBindings.kt

And put in:

@BindingAdapter("bind:customHeight")
fun setLayoutHeight(view: View, height: Float) {
    view.layoutParams = view.layoutParams.apply { this.height = height.toInt() }
}

And then you can use this in your layouts:

<EditText 
android:id="@+id/internalTopDisplay"
android:layout_width="match_parent"
bind:customHeight="@{loginVM.compact ? @dimen/verificationHeightCompact : @dimen/verificationHeightFull}"
android:layout_height="wrap_content"/>

Upvotes: 14

Malek Hijazi
Malek Hijazi

Reputation: 4162

You can add a binding adapter for android:layout_height that takes in a float.

For height:

@BindingAdapter("android:layout_height")
    public static void setLayoutHeight(View view, float height) {
        ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
        layoutParams.height = (int) height;
        view.setLayoutParams(layoutParams);
    }

For width:

@BindingAdapter("android:layout_width")
public static void setLayoutWidth(View view, float height) {
    ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
    layoutParams.width = (int) height;
    view.setLayoutParams(layoutParams);
}

Usage:

android:layout_width="@{model.isBig ? @dimen/big_dimen : @dimen/small_dimen, default=wrap_content}"

android:layout_height="@{model.isBig ? @dimen/big_dimen : @dimen/small_dimen, default=wrap_content}"

PS: Don't forget to set a default value

Upvotes: 3

Camino2007
Camino2007

Reputation: 790

For me did this the trick:

    @BindingAdapter("android:layout_height")
    public static void setLayoutHeight(View view, final Number height) {
        final ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
        layoutParams.height =  height.intValue();
        view.setLayoutParams(layoutParams);
    }

And the xml:

android:layout_height="@{item.isItemCollapsed ? @dimen/radar_item_first_background_height  : item.textMaxLines, default=@dimen/radar_item_first_background_height}"

Upvotes: 5

Linh
Linh

Reputation: 60999

In Java

@BindingAdapter("layout_height")
public static void setLayoutHeight(View view, float height) {
    LayoutParams layoutParams = view.getLayoutParams();
    layoutParams.height = height;
    view.setLayoutParams(layoutParams);
}

And in your XML

app:layout_height="@{ viewModel.isBig ? @dimen/dp_20 : @dimen/dp_5 }"

import the app like this

xmlns:app="http://schemas.android.com/apk/res-auto"

Upvotes: 58

KubaK
KubaK

Reputation: 304

According to the discussion on Android issue tracker, it is impossible to set layout height or width with data binding without creating custom binding adapters:

https://code.google.com/p/android/issues/detail?id=180666

The binding adapter needed for setting view height would look like that:

@BindingAdapter("android:layout_height")
public static void setLayoutHeight(View view, int height) {
    LayoutParams layoutParams = view.getLayoutParams();
    layoutParams.height = height;
    view.setLayoutParams(layoutParams);
}

Upvotes: 12

yigit
yigit

Reputation: 38263

When data binding is used, we strip values from the XML. You can add a default value to be used when it is stripped to avoid the issue.

see: http://developer.android.com/tools/data-binding/guide.html (bottom of the page).

android:layout_height="@{loginVM.compact ? @dimen/verificationHeightCompact : @dimen/verificationHeightFull, default=wrap_content}"

Upvotes: 35

Related Questions