I am trying to replicate this answer: Setting attribute of child element of included layout
I have a simple custom_edit_text.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="hint123" type="String" />
</data>
<android.support.design.widget.TextInputLayout
android:id="#+id/emailInputLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<android.support.v7.widget.AppCompatEditText
android:id="#+id/emailField"
android:layout_width="275dp"
android:layout_height="wrap_content"
android:paddingBottom="16dp"
android:paddingTop="14dp"
android:hint="#{hint123}"
android:textCursorDrawable="#null"
android:background="#drawable/edit_text_background"
android:fontFamily="#font/eina_regular"
android:textColor="#color/edit_text_color"
android:textColorHint="#color/edit_text_color"
android:textSize="15sp"
/>
</android.support.design.widget.TextInputLayout>
</layout>
And I include it in another file:
<?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">
<include
layout="#layout/custom_edit_text"
app:hint123="Email"/>
</layout>
However the project refuses to compile after a clean & rebuild with the error:
AAPT: error: attribute hint123 (aka inc.company.appname:hint123) not found.
Any ideas?
I also have
dataBinding {
enabled = true
}
enabled in the app level build.gradle
I think I've hit upon the solution. To activate data binding, you need to use a #{} expression, and what's in the braces must be valid Java code. So a literal string must be enclosed in quotes... which must be encoded as " inside an XML attribute value. Put it all together and you get:
<include
layout="#layout/custom_edit_text"
app:hint123="#{"Email"}"/>
Data binding does work for include files, it's just that the syntax for a literal is a bit convoluted. I had the same issue and this form is working in my project.
Bind the variables using #{ }
activity_main.xml
</layout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".simple.MainActivity">
<include layout="#layout/content_main_data_binding"
bind:name="#{ "Hello World" }" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
content_view.xml
</layout>
<data>
<variable
name="name"
type="String" />
</data>
<TextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{ name }"/>
</layout>
GL
Problem is in the included layout. You can not set attribute hint123 in it.
Also, take a note that include is not supported as a direct child of layout tag.
Update your included XML code as below:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include android:id="#+id/custom_edit_text"
layout="#layout/custom_edit_text" />
</android.support.constraint.ConstraintLayout>
</layout>
To set hint using databinding, you have to set it in your java or kotlin file.
Here is java code:
public class YourActivity extends AppCompatActivity {
YourActivityBinding mBinding;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.your_activity);
mBinding.customEditText.setHint123("Email");
mBinding.customEditText.executePendingBindings();
}
}
I am using Android 3.1.1. And the following code is working for me, and if you can use it you will be able to reuse "hint" as you desired. I have a slightly altered layout file (custom_edit_text.xml) as follows.
<?xml version="1.0" encoding="utf-8"?>
<data>
<variable name="cName" type="String" />
<variable name="user" type="your.package.name.User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.email}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.mobile}" />
<android.support.design.widget.TextInputLayout
android:id="#+id/emailInputLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<android.support.v7.widget.AppCompatEditText
android:layout_width="match_parent"
android:layout_height="50dp"
android:hint="#{cName.toString()}"
android:paddingBottom="16dp"
android:paddingTop="14dp"
android:textSize="15sp" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>
Above the second "type" is package name + User class name.
I create the "User" class in a separate file as follows.
public class User {
String email;
String mobile;
User(String email, String mobile) {
this.email = email;
this.mobile = mobile;
}
public String getEmail() {
return email;
}
public String getMobile() {
return mobile;
}
}
Inside MainActivity inside onCreate() I create the user object create the string and bind them.
String email = "xyz#yahoo";
String mobile = "9999";
User user = new User(email,mobile);
CustomEditTextBinding binding = DataBindingUtil.setContentView(this,R.layout.custom_edit_text) ;
binding.setCName("Yam May");
binding.setUser(user);
And I enabled binding in the app level build.gradle as you did.
A very detailed description about data binding can be found in https://www.vogella.com/tutorials/AndroidDatabinding/article.html
Just in case anyone misses one detail like me: you must enclose your parent layout content and the included layout content within "" and "". Without that binding is not used in a layout. I had the same issue when I had not enclosed my parent layout with these tags.
There are 2 alternatives to the quote-escaping solution #big_m provides.
You can single-quote the entire expression and use double-quotes around the string:
<include
layout="#layout/custom_edit_text"
app:hint123='#{"Email"}'/>
Or you can use backticks around the string:
<include
layout="#layout/custom_edit_text"
app:hint123="#{`Email`}"/>
Related
I've been searching this a lot today and only found some unclear answers regarding this question.
I'm having multiple TextView and EditText in a form, all of them being customised by my theme and i'd like to reuse the same included layout each time, but having parameters to it like the text inside the TextView or hint inside the EditText.
My form.xml right now:
<LinearLayout android: orientation="horizontal">
<TextView android:text="Username"/>
<EditText android:id="edittext_username"
android:hint="Enter username..."
android:inputType="text"/>
</LinearLayout>
<LinearLayout android: orientation="horizontal">
<TextView android:text="Password"/>
<EditText android:id="edittext_password"
android:hint="Enter password..."
android:inputType="password"/>
</LinearLayout>
... other fields ...
What i'd like in the main form.xml:
<LinearLayout android:orientation="vertical" ...>
<include layout="form_edittext"
app:textview_text="#{`Username`}"
app:edittext_hint="#{`Enter username...`}"
... other parameters ...
/>
<include layout="form_edittext"
app:textview_text="#{`Password`}"
app:edittext_hint="#{`Enter password...`}"
... other parameters ...
/>
</LinearLayout>
also the form_edittext.xml:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="textview_text" type="java.lang.String"/>
<variable name="edittext_id" type="java.lang.String"/>
<variable name="edittext_inputType" type="java.lang.String"/>
<variable name="edittext_hint" type="java.lang.String"/>
</data>
<TextView android:text="#{textview_text}"/>
<EditText android:id="#{edittext_id}"
android:hint="#{edittext_hint}"
android:inputType="#{edittext_inputType}"/>
</layout>
I'm still a beginner and i do not know if this is possible.
On this post, the guy replied he used data binding (as in example shown by me)
How to Re-using Layouts with <include/> with parameters? (you may see the first answer).
However, using this method give me the error of XML not recognizing identifiers like "textview_text" (the variables that i'm accessing via #{...}. If you got other solutions i'd appreciate if you share them. Cheers!
UPDATE on iCantC's answer: (but the textview's text and the hint remain empty).
Main layout:
<?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"
xmlns:tools="http://schemas.android.com/tools">
...
<include layout="#layout/activity_login_layout_input"
android:id="#+id/activity_login_layout_input_emailAddress"
app:textviewt="#{#string/email}" //the string is "Email Address"
app:edittexth="#{#string/emailhint}" /> //the string is "Your email address..."
...
</layout>
Included layout:
<layout>
<data>
<variable
name="textviewt"
type="String"/>
<variable
name="edittexth"
type="String"/>
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:showDividers="middle">
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{textviewt}"
android:textColor="#color/c1"
android:textSize="12sp"/>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingEnd="26dp">
<androidx.appcompat.widget.AppCompatEditText
android:id="#+id/edittext"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_gravity="center|start"
android:layout_marginStart="26dp"
android:background="#drawable/activity_login_background"
android:hint="#{edittexth}"
android:padding="6dp"
android:textAlignment="center"
android:textColor="#color/c2"
android:textColorHint="#color/grey"
android:textSize="14sp"/>
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="start"
android:adjustViewBounds="true"
android:src="#drawable/activity_login_edittext_drawable" />
</FrameLayout>
</LinearLayout>
</layout>
It seems like you want to include a common layout and then pass dynamic parameters from the main layout to included layout, here are the steps,
Step 1: Enable DataBinding in your project
//In the build.gradle file in the app module, add this
android {
...
dataBinding {
enabled = true
}
}
Step 2: Create your common_layout_included.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="username"
type="String"/>
</data>
<LinearLayout
..
>
<TextView
android:id="#+id/tv_username"
android:text="#{username}"/>
</LinearLayout>
</layout>
Step 3: Include common_layout_included.xml in your main_layout.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">
<LinearLayout
..
>
<include
android:id="#+id/includedLayout"
layout="#layout/common_layout_included"
app:username="#{#string/username}" // here we pass any String
/>
</LinearLayout>
</layout>
Step 4: Make sure you inflate the layout using DataBinding way
//In the onCreate of your Activity
val binding = DataBindingUtil.setContentView(this,R.layout.main_layout)
That's it, you are good to go. If still some error appears just do File -> Invalidate Caches/Restart
One last thing, I saw that you are assigning the id's to the view's dynamically android:id="#{edittext_id}", in all of my experience, I never really encountered a use case where I would be motivated to do this. I don't even know if it's possible and even if possible I doubt it's a good practice.
I have a XML field that I want to change based on an 'ObservableBoolean' like this:
<?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"
xmlns:tools="http://schemas.android.com/tools"
>
<data>
<variable
name="isColored"
type="androidx.databinding.ObservableBoolean"
/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.consoleco.console.customView.PerTextView
android:layout_width="0dp"
android:layout_height="48dp"
android:background="#{isColored ? ?attr/colorAccent : #color/grayBackground"
android:gravity="center"
android:text="#string/save"
android:textColor="#color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
In this line of code above:
android:background="#{isColored ? ?attr/colorAccent : #color/grayBackground"
we can't use two '?' mark (IDE give error: expected, got ?'
How can I resolve this problem?
You can use a BindingAdapter for this (add this in a class):
#BindingAdapter("isColored")
public static void isColored(View view, boolean isColored){
TypedValue typedValueColor = new TypedValue();
view.getContext().getTheme().resolveAttribute(R.attr.colorAccent, typedValueColor, true);
if(isColored){
view.setBackground(typedValueColor.data);
} else {
view.setBackground(view.getContext().getResources().getColor(R.color.grayBackground));
}
}
And in your xml layout replace this:
android:background="#{isColored ? ?attr/colorAccent : #color/grayBackground"
With this:
app:isColored="#{isColored}"
Yes that is true. you can not use ?attr in your expression.
For now only work you can do is use #color/colorAccent instead of ?attr/colorAccent
Full answer:
1. Enable data binding in app/build.gradle:
dataBinding {
enabled true
}
2. Use DataBindingUtil to set content view
java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DataBindingUtil.setContentView(this, R.layout.activity_engineering_mode_main);
}
3. Child item layout
You will see that I define 2 new attributes
values/bools.xml
<variable
name="textTitle"
type="String" />
<variable
name="buttonVisibility"
type="boolean" />
With textTitle, you can use any string from resource by #string/string_name
With buttonVisibility, you have to define bool resource type, and use #bool/bool_name
layout/item_engineering_list_row.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View" />
<variable
name="textTitle"
type="String" />
<variable
name="buttonVisibility"
type="boolean" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="#dimen/engineer_actionbar_height"
android:background="#color/engineer_background_color"
android:orientation="horizontal">
<TextView
android:id="#+id/engineer_txtName"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="#dimen/engineer_text_margin"
android:layout_weight="1"
android:gravity="center_vertical"
android:text="#{textTitle}"
android:textColor="#color/engineer_text_color"
android:textSize="#dimen/engineer_title_font_size" />
<Button
android:id="#+id/engineer_btnNext"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginRight="#dimen/engineer_text_margin"
android:text="BACK"
android:visibility="#{buttonVisibility ? View.VISIBLE : View.GONE, default=gone}" />
</LinearLayout>
</layout>
4. Boolean resource file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="item_button_visibility_default">false</bool>
<bool name="item_button_visibility_on">true</bool>
<bool name="item_button_visibility_off">false</bool>
</resources>
5. Parent layout, which includes some children and passes value to new attributes
<?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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="#+id/includedLayout0"
layout="#layout/item_engineering_list_row"
app:buttonVisibility="#{#bool/item_button_visibility_on}"
app:textTitle="#{#string/app_name}" />
<include
android:id="#+id/includedLayout1"
layout="#layout/item_engineering_list_row"
app:buttonVisibility="#{#bool/item_button_visibility_default}"
app:textTitle="#{#string/app_name}" />
</LinearLayout>
</layout>
Original Question:
I am new to android and I've been working with QML in QT for a time.
I wonder how I can make a layout more easier by applying params in XML in compound view components.
I have a custom layout item in xml and want to pass some attributes from a parent to its children, and I also want to initialize parent's attribute with new values to customize its children too.
My concept is as below:
item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
!!!! how to declare a new attribute here !!!
| like this:
| textTitle="New Title" // default value for child
| buttonVisibility="visible" // default value for child
">
<TextView
android:id="#+id/engineer_txtTitle"
android:text= textTitle <--- use parent's />
<Button
android:id="#+id/engineer_btnBack"
android:visibility= buttonVisibility <== use parent's />
</LinearLayout>
CLICK HERE TO SEE IMAGE: base Item
main.xml
<LinearLayout>
<include
android:id="#+id/item1"
layout="#layout/item"
textTitle= "FIRST"
// buttonVisibility not set here, use default as visible
/>
<include
android:id="#+id/item2"
layout="#layout/item"
textTitle= "SECOND"
buttonVisibility = "gone" // dont show button
/>
</LinearLayout>
CLICK HERE TO SEE IMAGE: apply with param
You can use Data Binding of Architecture component. Here is an sample of your requirement.
Recently I answered a question related to this.
Clean answer
This example shows pass value to <include.
I have a common view layout_common.xml, I want to pass String to included layout. I will create a variable of type String. Refer that String to your TextView. I created passedText for example.
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
>
<data>
// declare fields
<variable
name="passedText"
type="String"/>
</data>
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{passedText}"/> //set field to your view.
</layout>
Now you can pass passedText field to your <include tag.
activity_main.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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<include
android:id="#+id/includedLayout"
layout="#layout/layout_common"
app:passedText="#{#string/app_name}" // here we pass any String
/>
</LinearLayout>
</layout>
Note that both layouts (parent & included) should be binding layout, i.e. wrapped with <layout> and </layout> tags
Thanks #Khemraj to show the keyword "Data Binding" in Android :)
I have found the answer for me. It includes Khemraj's answer and some small code added to values resource.
I posted it in my question for others to find it easily.
When I build the project. I'm getting error on app:visibleGone
I'm also enable true to dataBinding in build.gradle and using android architecture components and mvvm.
project targetSdkVersion is 26 and support lib version is 26.0.1.
Below is the error message
error: package com.****.****.databinding does not exist
error: cannot find symbol class ActivityMainBinding
Cannot find the setter for attribute 'app:visibleGone' with parameter type boolean on android.widget.Button.
here is my activity_main.xml
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="loading"
type="boolean" />
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.aungmyolwin.importdb.MainActivity">
<Button
android:id="#+id/btn_load_sql"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Load from SQL"
app:visibleGone="#{!loading}"/>
<Button
android:id="#+id/btn_load_room"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Load from Room mapper"
app:visibleGone="#{!loading}"/>
<TextView
android:id="#+id/tv_import_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Importing database...."
app:visibleGone="#{loading}"/>
</LinearLayout>
</layout>
ActivityMain.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.btnLoadRoom.setOnClickListener(this);
binding.btnLoadSql.setOnClickListener(this);
viewModels= ViewModelProviders.of(this).get(MainActivityViewModels.class);
}
}
You need to create a custom BindingAdapter for app:visibleGone (because it is not a available method).
Like
public class BindingAdapters {
#BindingAdapter("visibleGone")
public static void showHide(View view, boolean show) {
view.setVisibility(show ? View.VISIBLE : View.GONE);
}
}
Moreover, if you don't want to define a method like this, you can do like
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="loading"
type="boolean" />
<import type="android.view.View"/> <!-- remember to import -->
</data>
<LinearLayout >
<Button
android:visibility="#{loading ? View.GONE : View.VISIBLE}"
</LinearLayout>
</layout>
I am using data binding, Here I am getting this issue:
Error:(252, 21) Cannot find the getter for attribute 'android:tag'
with value type java.lang.String on com.hdfcfund.investor.views.EditText.
Although, text attribute working fine but getting error while using tag element.
<?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="presenter"
type="com.hdfcfund.investor.folio.step4addnominee.AddNomineePresenter" />
<variable
name="nominee"
type="com.hdfcfund.investor.folio.step1.model.NewInvestorFolioRequest.Nominee" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white"
android:clickable="true">
<com.hdfcfund.investor.views.EditText
android:id="#+id/et_country"
style="#style/EditTextStyleRegularGrey15"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableRight="#drawable/ic_arrow_input"
android:focusableInTouchMode="false"
android:hint="#string/label_country_1"
android:inputType="text"
android:onClick="#{()-> presenter.onSpinnerClick(spinnerCountry)}"
android:tag="#={nominee.nomineeAddress.countryCode}"
android:text="#={nominee.nomineeAddress.countryName}" />
</RelativeLayout>
</layout>
The android:tag attribute doesn't support two-way binding by default. This is because there is no event mechanism to notify when the attribute changes.
You probably intended to use one-way binding:
android:tag="#{nominee.nomineeAddress.countryCode}"
There is no way for the user to change the tag value, so two-way really isn't of a lot of use with that attribute, anyway.
You need to define #InverseBindingAdapter to return value from property:
#InverseBindingAdapter(attribute = "android:tag")
public static String getStringTag(EditText view) {
return String.valueOf(view.getTag());
}