<merge/> in custom View xml layout - android

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:

Related

ImageView in SurfaceView without XML

My question is if is possible add an ImageView in a SurfaceView without XML. If yes, how? I have a main class that has the function of GamePanel, and for apply a Method i need to call it with an ImageView, but i don't know if it is possible. Thanks you in advance.
You need to read about the View and ViewGroups provided by the Android Framework.
I am giving the quick understanding to propose the solution.
Crash Course about View & ViewGroup
At the root of the Android UI system, everything is View.
What is a View?
It is a single widget / UI component that can be displayed on the screen. The View includes Buttons, TextViews, ImageViews, SurfaceView. They can not contain any child view i.e. They can not hold declaration for the any other child view
Following XML definition is incorrect: A view can not hold another view
<SurfaceView
android:id="#+id/textSurfaceView"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView android:id="#+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</SurfaceView>
What is ViewGroup?
Inherited from View and designed to contain and arrange more than one View also called as Child views. The various ViewGroups are LinearLayout, RelativeLayout, FrameLayout etc.
Following XML definition is Correct: A ViewGroup can hold another view
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="#+id/surfaceView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageView android:id="#+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</FrameLayout>
Here comes the solution
Step-1: Add a ViewGroup in your XML wrapping the existing SurfaceView. As mentioned already the ViewGroups are LinearLayout, RelativeLayout, FrameLayout etc.
res/layouts/your_layout.xml
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/baseFrame"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="#+id/surfaceView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</FrameLayout>
Step-2: At the time of view creation add an ImageView to the FrameLayout. onCreate() activity.
setContentView(R.layout.your_layout);
FrameLayout baseFrame = (FrameLayout) findViewById(R.id.baseFrame);
ImageView imageView = new ImageView(this);
imageView.setWidth(/*As per your need*/);
imageView.setHeight(/*As per your need*/);
imageView.setId(/*Any unique positive Number*/ R.ids.imageView1); <= Required to access this view later
/*Set the layout parameters such as layout_gravity as well.*/
baseFrame.addView(imageView);
Step-3: I know you must be wondering about the ImageView Id. I am giving the quicker way to assign an ID to a View.
Create a file ids.xml at res/values
Fill the following details.
<resources>
<item type="id" name="imageView1" />
</resources>
Step-4: Passing an ImageView to the method
ImageView myImageView = (ImageView) findViewById(R.id.imageView1);
methodToBeCalled(myImageView);
I hope that helps.
Happy Coding!!!

android double layout objects in compound view

When you create a compound view and inflate an xml layout file for it like this:
public class CompundLayout extends LinearLayout{...}
this inflates an xml with root like this:
<LinearLayout ... />
you end up with a layout hierarchy with a LinearLayout inside a LinearLayout (or so I concluded when defining a tag string to the layout object in the xml cased my app to crash).
Am I wrong? is there a better way to do this and prevent this double layout?
There is a better way to avoid the double layout, alter your xml layout to replace the LinearLayout container with a "merge" container. Your xml layout will look something like this afterwards:
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView ... />
<EditText ... />
...
</merge>

Xml layout to Java code generator

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.

Children of extended LinearLayout defined in XML do not show

In my project I have screens where the same pattern is repeated a lot - it's basically a container for views consisting of a linear layout with the heading, image and specific background. To avoid copying and pasting the same sequence multiple times I thought I could create a compound view, extend LinearLayout and define all the "styling" there, and then just use that component in my layouts.
I followed howto's and examples and got my compound view to work. However, all examples I've seen use the resulting view as follows:
<com.myproject.compound.myview
...some attrs...
/>
I.e. no children are added via XML. I need to use it like this:
<com.myproject.compound.myview
...some attrs...>
<TextView
..../>
...other views...
</com.myproject.compound.myview>
Since I'm extending LinearLayout I was expecting "myview" tag to work like LinearLayout too, but for some reason items I put inside do not get drawn. Is there something I need to do specially to get the inner views to draw?
My extended LinearLayout is very simple, I am not overriding any methods and just calling super in constructor and inflating the layout like this:
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.my_compound_view, this, true);
UPDATE: I thought I'd add an XML as a point of reference:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#drawable/bg"
android:padding="12dp">
<TextView
android:id="#+id/section_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FF0000AA" />
<ImageView
android:layout_width="match_parent"
android:layout_height="2dp"
android:src="#drawable/line" />
</LinearLayout>
Actually found a more elegant solution. Just need to use merge tag instead of LinearLayout in the compound view. All boils down to:
<merge xmlns:android="http://schemas.android.com/apk/res/android"
<TextView
android:id="#+id/section_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="HEADING"
android:textColor="#FF0000AA" />
<ImageView
android:layout_width="match_parent"
android:layout_height="2dp"
android:src="#drawable/line" />
</merge>
and
public class CompoundLayout extends LinearLayout{
public CompoundLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.compound_layout, this, true);
}
}
Main layout:
<com.testbench.CompoundLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFFFDDEE"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Inner text"
android:layout_gravity="center_horizontal"/>
</com.testbench.CompoundLayout>
After reading through the Android source and examples I think I figured this one out. Basically in my case it's a hybrid of Compound View and Custom Layout. "Compound view" part takes care about laying out and drawing the content of the XML which specifies the "container". But items inside that container get inflated later on and in my implementation they didn't get laid out.
One way is to follow the Custom Layout path - I'd have to implement onLayout() and onMeasure() to properly calculate my children (and I did during my research, it worked). But since I really do not need anything different than what LinearLayout does already and I don't want to copy/paste it's code (those methods are huge there), I just decided to override the method onFinishInflate() and added my "container view" there. Here is the whole code, please comment is something can be improved.
public class CompoundLayout extends LinearLayout{
View mView;
public CompoundLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mView = inflater.inflate(R.layout.compound_layout, this, false);
}
#Override
public void onFinishInflate(){
super.onFinishInflate();
addView(mView, 0);
}
}
Then in Activity's main layout I just use my custom layout the same way I would use LinearLayout. It renders the same, but always with those TextView and ImageView on top.

Include layout with custom attributes

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.

Categories

Resources