I'm building a complex layout and I want to use include tag for my custom component, like this:
<include layout="#layout/topbar"/>
The topbar layout has custom root component and in layout xml file it's defined like this:
<my.package.TopBarLayout
... a lot of code
Now, I wanna pass my custom defined attributes to "topbar" like this:
<include layout="#layout/topbar" txt:trName="#string/contacts"/>
And then get the value of those custom attributes in custom component code or ideally in xml.
Sadly, I cannot get value of txt:trName attribute to make it to the topbar layout, I just don't receive anything in code. If I understand correctly from that documentation page, I can set no attributes for layouts used via include, but id, height and width.
So my question is how can I pass my custom defined attributes to layout which is added via include?
I know this is an old question but I came across it and found that it is now possible thanks to Data Binding.
First you need to enable Data Binding in your project. Use DataBindingUtil.inflate (instead of setContentView, if it's Activity) to make it work.
Then add data binding to the layout you want to include:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="title" type="java.lang.String"/>
</data>
<RelativeLayout xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/screen_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:gravity="center">
...
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="20sp"
android:textStyle="bold"
android:text="#{title}"/>
...
</RelativeLayout>
</layout>
Finally, pass the variable from the main layout to the included layout 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">
<data>
...
</data>
...
<include layout="#layout/included_layout"
android:id="#+id/title"
app:title="#{#string/title}"/>
...
</layout>
It's not possible to attributes other than layout params, visibility or ID on an include tag. This includes custom attributes.
You can verify this by looking at the source of the LayoutInflater.parseInclude method, around line 705:
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.2_r1.1/android/view/LayoutInflater.java#640
The inflater only applies the ID and visibility attributes to the included layout.
I ran into this issue today. For whatever it is worth, I think there is a straight-forward work around. Instead of adding attributes to the include tag, create a custom wrapper view for the include and add attributes to that. Then, do the include from the wrapper. Have the wrapper class implementation extract the attributes and pass along to its single child, which is the root view of the include layout.
So, say we declare some custom attributes for a wrapper called SingleSettingWrapper like this -
<declare-styleable name="SingleSettingWrapper">
<attr name="labelText" format="string"/>
</declare-styleable>
Then, we create two custom view classes - one for the wrapper (SingleSettingWrapper) and one for the child (SingleSettingChild) that will be included -
<!-- You will never end up including this wrapper - it will be pasted where ever you wanted to include. But since the bulk of the XML is in the child, that's ok -->
<com.something.SingleSettingWrapper
android:id="#+id/wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
custom:labelText="#string/my_label_string">
<!-- Include the child layout -->
<include layout="#layout/setting_single_item"/>
</com.something.SingleSettingWrapper>
For the child, we can put whatever complex layout in there that we want. I'll just put something basic, but really you can include whatever -
<com.something.SingleSettingItem
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout >
<!-- add whatever custom stuff here -->
<!-- in this example there would be a text view for the label and maybe a bunch of other stuff -->
<!-- blah blah blah -->
</RelativeLayout>
</com.something.SingleSettingItem>
For the wrapper (this is the key), we read all of our custom attributes in the constructor. Then, we override onViewAdded() and pass those custom attributes to our child.
public class SingleSettingWrapper extends FrameLayout
{
private String mLabel;
public SingleSettingWrapper(Context context, AttributeSet attrs)
{
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.SingleSettingWrapper,
0, 0);
mLabel = a.getString(R.styleable.SingleSettingWrapper_labelText);
a.recycle();
}
public void onViewAdded(View child)
{
super.onViewAdded(child);
if (!(child instanceof SingleSettingItem))
return;
((TextView)child.findViewById(R.id.setting_single_label)).setText(mLabel);
/*
Or, alternatively, call a custom method on the child implementation -
((SingleSettingItem)child)setLabel(mLabel);
*/
}
}
Optionally, you can implement the child too and have it receive messages from the wrapper and modify itself (instead of having the wrapper modify the child as I did above).
public class SingleSettingItem extends LinearLayout
{
public SingleSettingItem(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public void setLabel(String l)
{
// set the string into the resource here if desired, for example
}
}
At the end of the day, each of the XML files where you wanted to <include> your layout will contain about 7 lines of XML for the wrapper+include instead of the single include that you wanted, but if the included view contains hundreds of lines you're still way better off. For example -
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<!-- this is the beginning of your custom attribute include -->
<com.something.SingleSettingWrapper
android:id="#+id/my_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
custom:labelText="#string/auto_lock_heading">
<include layout="#layout/setting_single_item"/>
</com.something.SingleSettingWrapper>
<!-- this is the end of your custom attribute include -->
</LinearLayout>
In practice, this seems to work pretty well and is relatively simple to set up. I hope it helps someone.
Unfortunately, the only thing I can contribute is that I was also unable to set custom attributes on an include tag, and have them pass through to the included layout.
It may well not be possible at this point.
It's not possible to use with custom attributes, or any attributes other than the ones stated on the API page (up through at least 5.0.0):
https://code.google.com/p/android/issues/detail?id=38023
http://grepcode.com/file/repo1.maven.org/maven2/org.robolectric/android-all/5.0.0_r2-robolectric-1/android/view/LayoutInflater.java
You have to include in your root xml element your custom namespace.
If your package name is com.example.test your xml shold be something like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:txt="http://schemas.android.com/apk/res/com.example.test" />
A nice tutorial is: http://blog.infidian.com/2008/05/02/android-tutorial-42-passing-custom-variables-via-xml-resource-files/
I had the same question. After visiting this thread, I ended up using View's setTag() methods to attach identifying information to each View during onCreate(), and then getTag() methods to retrieve it later on.
Related
My code currently requires a reference to a ListView in another XML file and activity. I have tried using the include keyword as so:
<include layout="#layout/cleareditems"/>
However, when I do this it merges the two layouts and you can see everything in clearedItems. What would I do just to reference that View and change it?
Thanks!
here is an example
<PATH_TO_YOUR_CLASS.YOUR_CLASS
android:id="#+id/search"//we give some name to it
android:layout_width="match_parent"
android:layout_height="wrap_content" />
and you should use merge tag, so the layout are not duplicated
<merge xmlns:android="http://schemas.android.com/apk/res/android">
//YOUR CUSTOM VIEW ELEMENTS, in this case its called search from above
</merge>
Does anybody know a tool to create java code from a xml layout file.
It would be useful, to create quickly a custom view (I do not want to create a separate library project) that I would like to include in an activities layout.
So lets say my custom view would be a Relative Layout with some child views.
It would be great if the tool could generate from a layout file like this:
<RelativeLayout 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">
<!-- some child Views -->
</RelativeLayout>
a java class like this:
class CustomView extends RelativeLayout{
/*
* Generate all the Layout Params and child Views for me
*/
}
And at the end I could use this generated class in a normal XML
<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"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
text="Hello World" />
<com.example.CustomView
android:layout_width="match_parent"
android:layout_height="100dp"
/>
</LinearLayout>
Does such a tool exists?
It would be useful, to create quickly a custom view (I do not want to
create a separate library project) that I would like to include in an
activities layout.
You can already do it. Create a custom view class and inflate custom layout there.
package com.example.view;
class CustomView extends LinearLayout {
public CustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
LayoutInflater.from(context).inflate(R.layout.custom_view, this, true);
}
}
Create a layout for that custom view class using <merge> tag as the root. Android will add content of tag into your custom view class, which is, in fact, LinearLayout in our case.
// custom_view.xml
<merge xmlns:android="http://schemas.android.com/apk/res/android"
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
text="Hello World" />
</merge>
You are done. Now you can add this custom class to your layout.
<com.example.view.CustomView
android:id="#id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
/>
No because there's 2 better ways to do it.
1)Use an <include> tag. That allows you to include a 2nd xml file.
2)Use a custom class, but have it inflate the second xml in its constructor. That way you can keep the layout in xml for the class.
Typically I use 2 if I want to create custom functionality where you set/change multiple values at one time, and 1 if I just want to break up my xml file into chunks.
I have the following custom view which is based on RelativeLayout:
public class LearningModeRadioButton extends
RelativeLayout
implements
Checkable,
View.OnClickListener {
private void init(Context context, AttributeSet attrs) {
LayoutInflater.from(context).inflate(R.layout.rb_learning_mode, this, true);
}
}
R.layout.rb_learning_mode contents are:
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
>
<RadioButton
android:id="#+id/rb"
android:button="#drawable/sel_rb_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextView
android:id="#+id/tv_mode_title"
style="#style/text_regular"
android:layout_toRightOf="#+id/rb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:id="#+id/tv_description"
style="#style/text_small"
android:layout_below="#+id/tv_mode_title"
android:layout_alignLeft="#+id/tv_mode_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</merge>
It sort of works, but layout parameters (layout_xxx) are ignored. I could use another <RelativeLayout/> as root element of the layout but I want to avoid having extra level in view hierarchy.
So the question is: How do I make layout attributes inside <merge/> work?
For anyone that might still be struggling with this, you should specify the parentTag attribute inside the merge tag. You will also need to specify layout_height and layout_width to make it work.
<merge
tools:parentTag="android.widget.RelativeLayout"
tools:layout_width="match_parent"
tools:layout_height="match_parent"
>
// Child Views
</merge>
The editor should display everything properly now.
Merge useful for LinearLayout and FrameLayout its not suitable for RelativeLayout.
Obviously, using works in this case because the parent of an activity's content view is always a FrameLayout. You could not apply this trick if your layout was using a LinearLayout as its root tag for instance. The can be useful in other situations though.
check this:
I have to create a list whose list items have 2 lines of text. I started building a custom list item, but then I discovered the TwoLineListItem component. I wrote this code:
pageFilterResultView=new TwoLineListItem(containerActivity);
pageFilterResultView.getText1().setText("Test");
However, getText1 returns null, and the second line throws a NullPointerException. So I thought I need to use an inflated layout instead of a constructor. The TwoLineListItem documentation specifies I can use the android.R.layout.two_line_list_item resource for the layout, so I changed the code to:
LayoutInflater inflater=(LayoutInflater)containerActivity.
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
pageFilterResultView=(TwoLineListItem)inflater.inflate(android.R.
layout.two_line_list_item,null);
pageFilterResultView.getText1().setText("Test");
However, this throws a ClassCastException because the layout is actually a LinearLayout. TwoLineListItem inherits from RelativeLayout, so I can't even cast the layout to a higher class in the hierarchy.
So the question is: How do I use TwoLineListItem correctly? Do I have to create my own custom layout for it? If so, what's the point of this component if I still have to do all the work of creating a list item by myself?
How do I use TwoLineListItem correctly?
The TwoLineListItem widget is a facade over two TextViews that have to be provided by you. To use the TwoLineListItem in a ListView's row you'll need a row layout where you have the TwoLineListItem widget with two(at least) TextView children with specific ids(android.R.id.text1 and android.R.id.text2). Something like this:
<?xml version="1.0" encoding="utf-8"?>
<TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="#android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="#android:id/text2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#android:id/text1"/>
</TwoLineListItem>
Then you can use it in the getView() method like you did:
pageFilterResultView=(TwoLineListItem)inflater.inflate(R.layout.the_layout_file_above,null);
pageFilterResultView.getText1().setText("Test");
Of course you have the possibility of using an included layout file as the child of the TwoLineListItem(as long as you have the two TextViews with the required ids):
<?xml version="1.0" encoding="utf-8"?>
<TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- the android version of the two line layout -->
<include layout="#android:layout/two_line_list_item" />
</TwoLineListItem>
but this just increases the layout depth and should be avoided.
If so, what's the point of this component if I still have to do all
the work of creating a list item by myself?
Judging by the fact that you can't use this widget programmatically, I don't see the need for this component either.
put your layout code inside try catch block with ClassCastException it will works fine my Friend . .....
In my layout xml file, I have included other layout xml file (each
with a different android id).
<include layout="#layout/view_contact_name" android:id="+id/test1"/>
<include layout="#layout/view_contact_name" android:id="+id/test2"/>
But when I run it in the emulator, and start Hierarchy Viewer, each of
the layout still shows 'NO_ID', and in my code, I have
findViewById(R.id.test1) and findViewById(R.id.test2) both returns null.
Can anyone please help me with my problem ?
Specify the ID in the <include>
<include layout="#layout/test" android:id="#+id/test1" />
Then use two findViewById to access fields in the layout
View test1View = findViewById(R.id.test1);
TextView test1TextView = (TextView) test1View.findViewById(R.id.text);
Using that approach, you can access any field in any include you have.
I found out, that if you are using <merge> tag in your include layout, then the ID of include transfers to the merge tag which is not real view.
So either remove merge, or replace it with some layout.
Tor Norbye wrote:
The <include> tag is not a real view, so findByView will not find it. The #id attribute (and any other attributes you've set on the include tag) gets applied on the root tag of the included layout instead. So your activity.getView(R.id.included1) should in fact be the <TextView> itself.
Romain Guy indicates that you can override the ID of an included layout by putting an android:id attribute inside the <include> tag.
<include android:id="#+id/cell1" layout="#layout/workspace_screen" />
I think the top answer misses the most important point and might mislead people into thinking the <include/> tag creates a View that holds the include contents.
The key point is that include's id is passed to the root view of the include's layout file.
Meaning that this:
// activity_main.xml
<include layout="#layout/somelayout" android:id="#+id/someid"/>
// somelayout.xml
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
Becomes this:
// activity_main.xml
<ImageView
android:id="#+id/someid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
yes is like this, but careful when the layout inserted in include field is a custom one and you want to access that root layout. That layout in this case #layout/test test, is actually returned in first line.
test test1View = (test)findViewById(R.id.test1);
you must set id each include tag
included child element set a
new id. if you look how to generate new id, look at this entry:
https://stackoverflow.com/a/15442898/1136117
Problem is we try to use id which is not declared in current layout file.
Instead of declaring again, id can be simply referred using #+id/. If you refactor original id name through Android Studio it does refactor in included layout as well.
<include layout="#layout/toolbar"/>
<TextView
android:id="#+id/txt_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
**android:layout_below="#+id/toolbar"**
android:layout_marginTop="16dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"/>
In a case of using <RecyclerView> find the id of <include> by using an instance of inflated view or else it will return null.
public class ViewHolder extends RecyclerView.ViewHolder {
private mTextView;
public ViewHolder(View view) {
super(view);
View include_1 = view.findViewById(R.id.include_1);
mTextView = (TextView) include_1.findViewById(R.id.text_id);
}
}
If you have set id to either root tag of included layout then you can use that id
or you can set id to included layout.
But you can not set id to both it may throw exception.
<include layout="#layout/view_contact_name" android:id="+id/test1"/>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
....
</LinearLayout>
Or
<include layout="#layout/view_contact_name"/>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="#+id/llBottomMainView"
android:layout_width="match_parent"
android:layout_height="wrap_content">
....
</LinearLayout>
When talking about include you either have an id on the root view inside the included layout file or on the include line itself and not on both. For example:
<include layout="#layout/layout1" android:id="#+id/layout1"/>
Layout 1 file
<RelativeLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/layout2">
</RelativeLayout>
The above example is wrong because technically you have two id's declared for the same layout. So what you have to do is pick which element will have the id.
To specify the id when you are including a xml file is like setting it to any xml element
Example:
*list_layout.xml*
`<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvNames"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>`
*activity_main.xml*
`<RelativeLayout
tools:context=".MainActivity">
<include
layout="#layout/list_layout"
android:id="#+id/myList" />
</RelativeLayout>`
Now if you want to get that to use in .kt file, just use normally findViewById
Exemplo
*MainActivity.kt*
`val myList: RecycleView = findViewById(R.id.myList)`
Wow, I can't believe this question doesn't have the right answer yet. It's simple tags suck. You can only change things that start with android:layout_ which android:id doesn't match. So the answer is you can't. Sorry. What you can do instead is create a class that will be a ViewGroup which will inflate the included views inside, then add that as a tag in your layout, but that's about it.