According to this CommonsWare example I managed to get my RelativeLayout subclass to be merged with my layout described in a xml layout with merge root. My only concern is that I cannot describe my RelativeLayout parameters in xml.
My xml layout:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:my="http://schemas.android.com/apk/res/hu.someproject"
android:layout_width="36dp"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:width="36dp" >
<RelativeLayout
android:id="#+id/upper_container"
style="?pretty_style"
android:layout_alignLeft="#+id/image_title"
android:layout_alignParentTop="true"
android:layout_alignRight="#+id/image_title"
android:layout_centerHorizontal="true" >
<View
android:id="#+id/upper_indicator"
android:layout_width="fill_parent"
android:layout_height="5dp"
android:layout_alignParentTop="true"
android:background="#color/mycolor" />
<TextView
android:id="#+id/text_degree"
style="?pretty_style"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="23°" />
</RelativeLayout>
<hu.MyView
android:id="#+id/image_title"
style="?my_image_title"
android:layout_below="#+id/upper_container"
android:layout_centerHorizontal="true"
my:myColor="#2f8741"/>
I think the problem is that merge happens to the children of the merge tag, and not the merge itself. Any idea how can I get my parameters in the merge to affect my RelativeLayout?
My RelativeLayout subclass, without package declaration and imports:
public class MyRelativeLayoutSubclass extends RelativeLayout {
public MyRelativeLayoutSubclass(Context context) {
super(context);
initTile(null);
}
public MyRelativeLayoutSubclass(Context context, AttributeSet attrs) {
super(context, attrs);
initTile(attrs);
}
public MyRelativeLayoutSubclass(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initTile(attrs);
}
private void initTile(Object object) {
final LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.my_great_little_layout, this, true);
}
}
I know I can add everything to a LayoutParams, and then add my MyRelativeLayoutSubclass with that LayoutParams, but I would like escape that, that's a lot of unnecessary code.
I think the problem is that merge happens to the children of the merge tag, and not the merge itself.
AFAIK, you are correct. <merge> is a placeholder, not a ViewGroup.
I know I can add everything to a LayoutParams, and then add my MyRelativeLayoutSubclass with that LayoutParams, but I would like escape that, that's a lot of unnecessary code.
Create an XML layout file containing a MyRelativeLayoutSubclass element, and put your attributes there. Then, inflate that layout.
Extracting all the attributes into Style was a solution for me. As a bonus it's screen-size dependent unlike the hard-coded attributes or Java-code. Maybe you can go further and put it into the Theme attributes.
<com.example.widget.MyCustomView
android:id="#+id/my_custom_view"
style="#style/MyCustomView.Default"/>
Related
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:
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.
I'm makeing a project that can show defferent watches to users.The watch has different clock dial.I want to make two different widgets(the watch)in one screen and the watch has different time and clock dial.How can I do it?
You can make your own control and then include that control twice in a single screen.
package com.sample.ui.control;
public class MyControl extends LinearLayout {
public MyControlContext context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.control_my_control, this);
}
}
You can add extra code as necessary. The xml file control_my_control is the XML for your watch. In that XML, be sure the outer element is a element.
For the view that will contain two of these, you use them like:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent">
<com.sample.ui.control.MyControl android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="#+id/control1" />
<com.sample.ui.control.MyControl android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="#+id/control2" />
</LinearLayout>
I have a custom made UI component which have inside another custom made UI component (which is separated for convenience purposes).
I want to be able to pass my own attributes to the father component, and read them inside the sub-component. In that way, the only thing the developer has to see is the father component, w/o needing to know there's another component inside.
For instance, this is the application's main layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:udinic="http://schemas.android.com/apk/res/com.udinic"
android:id="#+id/main"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<com.udinic.FatherComponent android:id="#+id/fatherComp"
android:layout_width="fill_parent"
udinic:itemNum="9"
android:layout_height="wrap_content" />
</LinearLayout>
and the father component's xml looks like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:udinic="http://schemas.android.com/apk/res/com.udinic"
android:id="#+id/fatherLayout"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<com.udinic.SubComponent android:id="#+id/subComp"
android:layout_width="fill_parent"
udinic:itemNum=<<Get the itemNum passed to me>>
android:layout_height="wrap_content" />
</LinearLayout>
I didn't find any way to do this using the XMLs only. Does anyone knows anything that can help solving this?
Thanks
You'll need to use the constructor of your father class to pass the value into the sub class.
public FatherClass(Context context, AttributeSet attrs)
{
super(context, attrs, LAYOUT);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomLayout);
//for string attribute
textView.setText(getStringAttribute(typedArray, R.styleable.CustomLayout_labelText));
//for resources
int textAppearance = typedArray.getResourceId(R.styleable.CustomLayout_textAppearance, -1);
textView.setTextAppearance(context, textAppearance);
//for integers
int inputType = typedArray.getInt(R.styleable.CustomLayout_inputType, -1);
editText.setInputType(inputType);
}
When I switch to landscape mode, the following custom view throws an exception in Android 1.5r3 cupcake:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.opentable/com.opentable.activity.SearchResults}:
java.lang.IllegalArgumentException: Wrong state class -- expecting View State
My code:
public class TextProgressBar extends LinearLayout {
public TextProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.text_progress_bar, this, true);
setGravity(Gravity.CENTER);
}
public TextProgressBar(Context context) {
this(context,null);
}
}
The XML for this view is fairly straightforward:
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:id="#+id/progress" />
<TextView
android:text="Loading..."
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
Any ideas what might be happening?
Ah, the problem would have been difficult to diagnose as originally stated.
Turns out that inside my custom view my ProgressBar was named #+id/progress, but when I used the custom view TextProgressBar in my layout I also called the TextProgressBar #+id/progress, resulting in two views with the same id.
Renaming one of them fixed the problem.
I was using viewpager with fragments. First, I have tested each fragment for viewpager to check where actually error occurred. Finally, I figured out that I was using ids of the layouts with the same name. Then I have changed the ids of the views.