I'm trying to setup a relationship hierarchy between objects. Every object has a parent of the same type as itself, or null.
I have a main.xml that contains some of these:
<com.morsetable.MorseKey
android:id="#+id/bi"
android:layout_weight="1"
custom:code=".."
custom:parentKey="#id/be"
android:text="#string/i" />
a res/values/attrs.xml that contains one of these:
<declare-styleable name="MorseKey">
<attr name="code" format="string"/>
<attr name="parentKey" format="reference"/>
</declare-styleable>
and a class (that is not my activity) that contains this:
public class MorseKey extends Button {
public MorseKey(Context context, AttributeSet attrs) {
super(context, attrs);
initMorseKey(attrs);
}
private void initMorseKey(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.MorseKey);
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.MorseKey_code:
code = a.getString(attr);
break;
case R.styleable.MorseKey_parentKey:
parent = (MorseKey)findViewById(a.getResourceId(attr, -1));
//parent = (MorseKey)findViewById(R.id.be);
Log.d("parent, N:", ""+parent+","+N);
break;
}
}
a.recycle();
}
private MorseKey parent;
private String code;
}
This isn't working. Every MorseKey instance reports N == 2 (good) and parent == null (bad). More, parent == null even when I explicitly try setting it to some arbitrary value (see comment). I have also tried custom:parentKey="#+id/be" (with the plus sign) but that didn't work either. What am I doing wrong?
If your MorseKey class is in a separate java file, which I assume is the case from your statment "a class (that is not my activity)". Then I believe the problem is in your use of findViewById(). findViewById() will look for a resource within the MorseKey view itself rather than the main.xml file.
Maybe try getting the parent of the MorseKey instance and calling parent.findViewById().
case R.styleable.MorseKey_parentKey:
parent = this.getParent().findViewById(a.getResourceId(attr, -1));
Though this will only work if your MorseKey parent and child are in the same layout.
<LinearLayout ...>
<MorseKey ..../><!-- parent -->
<MorseKey ..../><!-- child -->
</LinearLayout>
But it would be quite difficult to find the view if your layout is something like this with the parent and child in separate layouts.
<LinearLayout ...>
<MorseKey ..../><!-- parent -->
</LinearLayout>
<LinearLayout ...>
<MorseKey ..../><!-- child -->
</LinearLayout>
Related
I'm building an android compound control and nesting it into another compound control. The nested control, ThirtySecondIncrement, is a simple incrementing control with a minus then text field then plus so you can raise or lower the increment. I've made this control more general for my app allowing for a simple counter or 30-second increments or 1-minute increments. Here is the xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="counter_simple">0</integer>
<integer name="counter_30sec">1</integer>
<integer name="counter_1min">2</integer>
<declare-styleable name="ThirtySecondIncrement">
<attr name="countertype" format="integer"/>
<attr name="maxcount" format="integer"/>
</declare-styleable>
<declare-styleable name="IntervalEdit">
<attr name="label" format="string"/>
<attr name="incrementlabel" format="string"/>
</declare-styleable>
</resources>
My outer control includes labels and the ThirtySecondIncrement control. I would like to make the outer control flexible enough that I could include the "countertype" style to the outer control.
Can I do this in xml or must I do it programmatically? And if I do it programmatically how can I guarantee that it is done before the control is first used. Here is the code to extract the xml attributes:
public class ThirtySecondIncrement extends LinearLayout {
final int COUNT_INTEGER = 0;
final int COUNT_30SEC = 1;
final int COUNT_1MIN = 2;
//other code
public ThirtySecondIncrement(Context context, AttributeSet attr) {
super(context, attr);
TypedArray array = context.obtainStyledAttributes(attr, R.styleable.ThirtySecondIncrement, 0, 0);
m_countertype = array.getInt(R.styleable.ThirtySecondIncrement_countertype, COUNT_30SEC);
m_max = array.getInt(R.styleable.ThirtySecondIncrement_maxcount, MAXCOUNT);
m_increment = (m_countertype == COUNT_1MIN) ? 2 : 1;
array.recycle();
Initialize(context);
}
In a similar function in my IntervalEdit I could get an attribute relating to the counter and use a public function in ThirtySecondIncrement to set the countertype but, as stated, I'm wondering if there's a way to do this in xml.
thanks, in advance
I waited a while but got no answer so I solved the problem by having a helper function in the nested control to enable to set the property at runtime.
public void SetCounterType(integer countertype) {
//after checking that countertype is a valid value
m_countertype = countertype;
}
then in the constructor for IntervalEdit:
public IntervalEdit(Context context, AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.IntervalEdit, 0, 0);
//other attributes read here
m_counterstyle = array.getInt(R.styleable.IntervalEdit_counterstyle, R.integer.counter_simple);
(ThirtySecondIncrement)findViewById(R.id.tsi).SetCounterStyle(m_counterstyle);
The SetCounterStyle function in the first custom control only sets the variable and doesn't force a redraw enabling me to call it from within the including custom control's constructor.
Any, that's how I solved it.
I've been creating apps without much XML, creating views programmatically. I'd like to switch to XML. So I wrote an XML file for a RelativeLayout, and I need to inflate it into an existing class (a subclass of RelativeLayout, of course) that has all the implementation logic.
How do I inflate into "this" in the constructor?
By the way, what's really the advantage of XML? When I create views in the code, I scale fonts and images and also move views around depending on the screen's size, orientation, aspect ratio, etc. With XML approach, I'd have to create a separate XML for all possible configurations...
Constructor code:
public OrderEditControl()
{
super(LmcActivity.W.getApplicationContext());
Resources res = LmcActivity.W.getResources();
setBackgroundColor(Color.TRANSPARENT);
headers = res.getStringArray(R.array.item_list_columns);
widths = new int[headers.length];
createLabels();
createButtons();
LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
lp.addRule(ALIGN_PARENT_TOP);
lp.addRule(RIGHT_OF, labels[LabelType.CUSTOMER.ordinal()].getId());
lp.addRule(LEFT_OF, buttons[ButtonType.FIND_CUSTOMER.ordinal()].getId());
customerView = new TextView(LmcActivity.W.getApplicationContext());
customerView.setTextColor(Color.BLACK);
customerView.setId(400);
customerView.setTypeface(Typeface.DEFAULT_BOLD);
customerView.setGravity(Gravity.CENTER_VERTICAL);
addView(customerView, lp);
lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp.addRule(ALIGN_TOP, labels[LabelType.SHIP_TYPE.ordinal()].getId());
lp.addRule(ALIGN_BOTTOM, labels[LabelType.SHIP_TYPE.ordinal()].getId());
lp.addRule(RIGHT_OF, labels[LabelType.SHIP_TYPE.ordinal()].getId());
shipSpinner = new Spinner(LmcActivity.W);
shipSpinner.setId(401);
shipSpinner.setAdapter(shipAdapter);
shipSpinner.setOnItemSelectedListener(this);
addView(shipSpinner, lp);
deliveryView = new EditText(LmcActivity.W.getApplicationContext());
deliveryView.setGravity(Gravity.CENTER_VERTICAL);
deliveryView.setSingleLine();
deliveryView.setId(402);
addView(deliveryView);
lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
lp.addRule(RIGHT_OF, labels[LabelType.COMMENTS.ordinal()].getId());
lp.addRule(LEFT_OF, buttons[ButtonType.ITEMS.ordinal()].getId());
lp.addRule(ALIGN_TOP, labels[LabelType.COMMENTS.ordinal()].getId());
commentView = new EditText(LmcActivity.W.getApplicationContext());
commentView.setGravity(Gravity.TOP);
commentView.setId(403);
addView(commentView, lp);
lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
lp.addRule(BELOW, commentView.getId());
itemList = new ListView(LmcActivity.W.getApplicationContext());
itemList.addHeaderView(createRow(null, null), null, false);
itemList.setOnItemClickListener(this);
itemList.setAdapter(itemAdapter);
itemList.setCacheColorHint(0);
itemList.setBackgroundColor(Color.TRANSPARENT);
itemList.setId(404);
addView(itemList, lp);
lays[0] = new LayParm(false);
lays[1] = new LayParm(true);
}
/** create the view's buttons */
private void createButtons()
{
for (int i = 0; i < N_BUT; ++i)
{
Button but = i == ButtonType.ITEMS.ordinal() ?
new TextGlassButton(2.4f, LmcActivity.W.getResources().getString(R.string.items), Color.WHITE) :
new EffGlassButton(1.2f, butEffects[i]);
but.setId(BUT_ID + i);
but.setOnClickListener(this);
buttons[i] = but;
if (i == ButtonType.DATE.ordinal())
addView(but);
else
{
LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
if (i < 2)
lp.addRule(ALIGN_PARENT_TOP);
else
lp.addRule(BELOW, BUT_ID + i - 2);
if (i % 2 == 0)
lp.addRule(ALIGN_PARENT_RIGHT);
else
lp.addRule(LEFT_OF, BUT_ID + i - 1);
addView(but, lp);
}
}
}
/** create text labels */
private void createLabels()
{
Paint paint = AFDraw.W.textPaint;
paint.setTextSize(Universe.TEXT_SIZE);
paint.setTypeface(LmcActivity.W.defaultTypeface);
String[] titles = LmcActivity.W.getResources().getStringArray(R.array.order_labels);
for (int i = 0; i < titles.length; ++i)
{
LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp.addRule(ALIGN_PARENT_LEFT);
if (i == 0)
lp.addRule(ALIGN_PARENT_TOP);
else
lp.addRule(BELOW, LABEL_ID + i - 1);
TextView tv = new TextView(LmcActivity.W.getApplicationContext());
tv.setText(titles[i]);
tv.setTextColor(Color.BLACK);
tv.setId(LABEL_ID + i);
tv.setTypeface(LmcActivity.W.defaultTypeface);
tv.setGravity(Gravity.CENTER_VERTICAL);
labels[i] = tv;
addView(tv, lp);
labelWidth = Math.max(labelWidth, paint.measureText(titles[i]));
}
labelWidth += Universe.TEXT_SIZE * 0.5f;
dateWidth = paint.measureText("00/00/00") + Universe.TEXT_SIZE * 1.5f;
}
#scriptocalypse is generally right, but subclassing some layouts and inflating custom layout to this class helps to separate different abstractions. There are so many bad tutorials, in which everything is done in the Activity. I see that the world's new comming programmers will code only crap looking applications.
With custom layout you can do in Activity only such a thing:
medicineView.putMedicine(medicineList);
instead of all crappy adapter creations and looking for views...
Firstly you should create some view for your custom View:
<RelativeLayout ...>
<!-- You put all your views here -->
</RelativeLayout>
Secondly if you are sattisfied with your view, you should change the root to merge tag:
<merge ...>
<!-- You put all your views here -->
</merge>
This is very important. We begin design with RelativeLayout tags in order to IDE know how to draw layouts, and how to do completions. But if we leave it as it is, we will end up in two nested RelativeLayouts it will be something like that in the end:
<RelativeLayout ...> <!-- That is your class -->
<RelativeLayout ...> <!-- This is inflated from layout -->
<!-- You put all your views here -->
</RelativeLayout>
</RelativeLayout>
If you change your layout to "merge" then it will look like this:
<RelativeLayout ...> <!-- That is your class -->
<merge...> <!-- This is inflated from layout -->
<!-- You put all your views here -->
</merge>
</RelativeLayout>
and will be merged to its root:
<RelativeLayout ...> <!-- That is your class, merged with layout -->
<!-- You put all your views here -->
</RelativeLayout>
At the end you must subclass demanded View or ViewGroup:
public class CustomView extends RelativeLayout {
public CustomView(Context context) {
super(context);
initialize();
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public CustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
private void initialize() {
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.id.your_layout, this, true);
// find your views, set handlers etc.
}
}
Usage
Just like #scriptocalypse already said. In another layout you use this like that:
<SomeLayout>
<com.foo.CustomView>
</SomeLayout>
First, to answer your main question:
you would not want to inflate an XML RelativeLayout into your RelativeLayout class. You'd extend RelativeLayout and then declare an instance of your RelativeLayout in an XML file, like so:
// com.foo.MyRelativeLayout.java
public class MyRelativeLayout extends RelativeLayout{
/**
* Implement MyRelativeLayout
*/
}
and...
// layout_example.xml
<?xml version="1.0" encoding="utf-8"?>
<com.foo.MyRelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- Put More Views in here... -->
<TextView
android:id="#+id/customer_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/customer_name_placeholder" />
<!-- and on... -->
</com.foo.MyRelativeLayout>
But more to the point, if you're using the XML to lay out your file, you don't need any of those instantiations or .addRule() method invocations inside your MyRelativeLayout file because you've done it declaratively in XML instead.
To answer your second question of "Why do you want to use XML anyway?"
There are many reasons. Maybe these apply to you, and maybe they don't, but they're ones that I can think of fairly easily that have been relevant in my work.
You don't actually have to create a new layout file for every separate screen size or use case. For the most part, a single layout file will suffice for most screens. You might find that you will have size/resolution/orientation specific dimens.xml or style.xml files, but unless you want a dramatically different arrangement for your different possibilities then the layouts themselves don't repeat themselves too often.
You can use a visual editor. This is important if you're working in teams, and your teammates don't like to or want to use only Java to lay out their screens. While I and others gladly create View and Layout subclasses to fit our needs, I know of literally nobody who prefers to use Java as their primary layout language. Finding people who will work with you (or a job where everyone else uses the XML tools) could be challenging.
If you're creating tools for other people to use (like the above-mentioned folks who prefer XML) you can actually give them custom attributes to work with, that make positioning and layout more powerful. These attributes could be hard-coded in the XML, or they could be references to any of the other Android resources (drawable/string/color/integer/boolean/etc...). As a contrived example, but one based on your code, you could give your users the ability to specify a number of buttons to create rather than rely on the N_BUT variable. You could give it a default value, but offer users a way to change it in XML.
Here is an example:
// somelayout.xml
<?xml version="1.0" encoding="utf-8"?>
<com.foo.MyRelativeLayout
xmlns:param="http://schemas.android.com/apk/res-auto"
style="#style/MyRelativeLayoutStyle"
param:numberOfButtons="3">
</com.foo.MyRelativeLayout>
and in a different file...
//attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyRelativeLayout">
<attr name="numberOfButtons" format="reference|integer" />
</declare-styleable>
</resources>
and in your MyRelativeLayout, you access those attributes from the AttributeSet in its constructor (the one called by Android when it uses XML to create a layout).
Using the style="#style/foo" syntax can allow you to create "classes" of styles that can apply to all kinds of views without actually making a View subclass. Let's say you know that you always want to have a set of parameters that hold true for all your Button elements but don't want to subclass Button.
For example:
// styles.xml
<style name="BaseButton">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:focusable">true</item>
<item name="android:clickable">true</item>
<item name="android:background">#drawable/bg_common_button</item>
<item name="android:textColor">#color/white</item>
<item name="android:textSize">#dimens/base_button_text_size</item>
<!-- ^^ that dimen value could vary from screen size to screen size, but the style likely won't -->
</style>
// button_layout.xml
<Button
android:id="#+id/styled_button"
style="#style/BaseButton" /> <!-- and you're done -->
// some_other_layout.xml
<LinearLayout
style="#style/BaseLinearLayout">
<Button style="#style/BaseButton" android:text="Button1" />
<Button style="#style/BaseButton" android:text="Button2" />
<Button style="#style/BaseButton" android:text="Button3" />
</LinearLayout>
If you would like to instantiate that button using code, then you can use the LayoutInflater to inflate that specific button's layout and use that wherever you want. In fact, you can create all manner of components in XML and then inflate them at runtime.
LayoutInflater inflater = LayoutInflater.from(YourActivity.this);
Button theInflatedButton = inflater.inflate(R.layout.button_layout.xml, null);
Of course, the canonical example is ListViews and the items that you wish to populate them. You'd create a listview item layout xml and then inflate that whenever your Adapter is in need of a new convertView instance.
I want to write a custom view that requires some button with a speicific id to be present (to be able to be found by id), as in ListActivity android.R.id.list must be present, how do i do the same with a custom id of my own?
I plan on reusing this view in an Android lib for several use cases, but all of them must declare some specific view with specific id so that i can find it by id in the lib code and manipulate it for later use in using Applications...
Just do what the ListActivity does.
Check for the ID in your custom view and throw an exception if it does not exist in the layout.
Snippet from ListActivity Source:
#Override
public void onContentChanged() {
super.onContentChanged();
View emptyView = findViewById(com.android.internal.R.id.empty);
mList = (ListView)findViewById(com.android.internal.R.id.list);
if (mList == null) {
throw new RuntimeException(
"Your content must have a ListView whose id attribute is " +
"'android.R.id.list'");
}
The best way to do this is in a flexible way is to use a custom attribute. It allows for any id to be the required id, but it also uses the dev tools to enforce that a valid id is used.
Declare that your custom view is style-able with a custom attribute in an attrs.xml file like this:
<resources>
<declare-styleable name="MyView">
<attr name="required_view_id" format="integer" />
</declare-styleable>
</resources>
You can then refer to the attribute from a layout file like below. Pay special attention to the header where the "app" namespace is defined. You can use any name you want for your custom attribute namespace, but you have to declare it to use any of your custom attributes when defining the views later. Note the custom attribute on MyView.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:id="#+id/the_id_of_the_required_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.full.package.to.MyView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:required_view_id="#id/the_id_of_the_required_view" />
</LinearLayout>
Now you need to make sure this view is present in your custom view class. You can required that your custom attribute is set by overriding certain constructors. You'll also need to actually verify the presence of the required view at some point. Here's a rough idea:
public class MyView extends View {
private int mRequiredId;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
checkForRequiredViewAttr(context, attrs);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
checkForRequiredViewAttr(context, attrs);
}
// Verify that the required id attribute was set
private void checkForRequiredViewAttr(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.MyView, 0, 0);
mRequiredId = a.getResourceId(R.styleable.MyView_required_view_id, -1);
a.recycle();
if (mRequiredId == -1) {
throw new RuntimeException("No required view id attribute was set");
}
}
// This allows the custom view to be programmatically instantiated, so long as
// the required id is manually set before adding it to a layout
public void setRequiredId(int id) {
mRequiredId = id;
}
// Check for the existence of a view with the required id
#Override
protected void onAttachedToWindow() {
View root = getRootView();
View requiredView = root.findViewById(mRequiredId);
if (requiredView == null) {
throw new RuntimeException(
String.format("Cannot find view in layout with id of %s", mRequiredId));
}
super.onAttachedToWindow();
}
}
Using onAttachedToWindow to check for the required view may not be good enough for your purposes. It won't, for example, prevent the required view from being removed. Finding a view in a layout isn't a cheap operation, especially for complex layouts, so you shouldn't constantly check for it.
I have a custom control (very simple for now) that is like a button. It needs to display an unpressed and a pressed image. It appears multiple times in the activity and has different pairs of images depending on where it's used. Think of toolbar icons - similar to that.
Here's an extract of my layout:
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:MyApp="http://schemas.android.com/apk/res/com.example.mockup"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TableRow>
<com.example.mockup.ImageGestureButton
android:id="#+id/parent_arrow"
android:src="#drawable/parent_arrow"
MyApp:srcPressed="#drawable/parent_arrow_pressed"
... />
...
</TableRow>
</TableLayout>
attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ImageGestureButton">
<attr name="srcPressed" format="reference" />
</declare-styleable>
</resources>
And, in the R.java, one finds:
public static final class drawable {
public static final int parent_arrow=0x7f020003;
public static final int parent_arrow_pressed=0x7f020004;
...
}
During widget instantiation, I want to determine the ids declared in the activity xml. How do I do that? I've tried this (I updated my original post with working code; so, the following works.)
public class ImageGestureButton extends ImageView
implements View.OnTouchListener
{
private Drawable unpressedImage;
private Drawable pressedImage;
public ImageGestureButton (Context context, AttributeSet attrs)
{
super(context, attrs);
setOnTouchListener (this);
unpressedImage = getDrawable();
TypedArray a = context.obtainStyledAttributes (attrs, R.styleable.ImageGestureButton, 0, 0);
pressedImage = a.getDrawable (R.styleable.ImageGestureButton_srcPressed);
}
public boolean onTouch (View v, MotionEvent e)
{
if (e.getAction() == MotionEvent.ACTION_DOWN)
{
setImageDrawable (pressedImage);
}
else if (e.getAction() == MotionEvent.ACTION_UP)
{
setImageDrawable (unpressedImage);
}
return false;
}
}
If you want to get the drawable use TypedArray.getDrawable(). In your example you are using getString().
In your declare-styleable use
<attr name="srcPressed" format="reference" />
If you want the actual resource ID for the Drawable, rather than the fully resolved Drawable it's self, you can do this:
TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.FooLayout );
TypedValue value = new TypedValue();
a.getValue( R.styleable.FooLayout_some_attr, value );
Log.d( "DEBUG", "This is the actual resource ID: " + value.resourceId );
I'm trying to create a custom Android control that contains a LinearLayout. You can think of it as an extended LinearLayout with fancy borders, a background, an image on the left...
I could do it all in XML (works great) but since I have dozens of occurences in my app it's getting hard to maintain. I thought it would be nicer to have something like this:
/* Main.xml */
<MyFancyLayout>
<TextView /> /* what goes inside my control's linear layout */
</MyfancyLayout>
How would you approach this? I'd like to avoid re-writing the whole linear layout onMeasure / onLayout methods. This is what I have for the moment:
/* MyFancyLayout.xml */
<TableLayout>
<ImageView />
<LinearLayout id="container" /> /* where I want the real content to go */
</TableLayout>
and
/* MyFancyLayout.java */
public class MyFancyLayout extends LinearLayout
{
public MyFancyLayout(Context context) {
super(context);
View.inflate(context, R.layout.my_fancy_layout, this);
}
}
How would you go about inserting the user-specified content (the TextView in main.xml) in the right place (id=container)?
Cheers!
Romain
----- edit -------
Still no luck on this, so I changed my design to use a simpler layout and decided to live with a bit of repeated XML. Still very interested in anyone knows how to do this though!
This exact question bugged me for some time already but it's only now that I've solved it.
From a first glance, the problem lies in the fact that a declarative content (TextView in Your case) is instantiated sometime after ctor (where we're usually inflating our layouts), so it's too early have both declarative and template content at hand to push the former inside the latter.
I've found one such place where we can manipulate the both: it's a onFinishInflate() method. Here's how it goes in my case:
#Override
protected void onFinishInflate() {
int index = getChildCount();
// Collect children declared in XML.
View[] children = new View[index];
while(--index >= 0) {
children[index] = getChildAt(index);
}
// Pressumably, wipe out existing content (still holding reference to it).
this.detachAllViewsFromParent();
// Inflate new "template".
final View template = LayoutInflater.from(getContext())
.inflate(R.layout.labeled_layout, this, true);
// Obtain reference to a new container within "template".
final ViewGroup vg = (ViewGroup)template.findViewById(R.id.layout);
index = children.length;
// Push declared children into new container.
while(--index >= 0) {
vg.addView(children[index]);
}
// They suggest to call it no matter what.
super.onFinishInflate();
}
A labeled_layout.xml referenced above is not unlike something like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation ="vertical"
android:layout_width ="fill_parent"
android:layout_height ="wrap_content"
android:layout_marginLeft ="8dip"
android:layout_marginTop ="3dip"
android:layout_marginBottom ="3dip"
android:layout_weight ="1"
android:duplicateParentState ="true">
<TextView android:id ="#+id/label"
android:layout_width ="fill_parent"
android:layout_height ="wrap_content"
android:singleLine ="true"
android:textAppearance ="?android:attr/textAppearanceMedium"
android:fadingEdge ="horizontal"
android:duplicateParentState="true" />
<LinearLayout
android:id ="#+id/layout"
android:layout_width ="fill_parent"
android:layout_height ="wrap_content"
android:layout_marginLeft ="8dip"
android:layout_marginTop ="3dip"
android:duplicateParentState="true" />
</LinearLayout>
Now (still omitting some details) elsewhere we might use it like this:
<com.example.widget.LabeledLayout
android:layout_width ="fill_parent"
android:layout_height ="wrap_content">
<!-- example content -->
</com.example.widget.LabeledLayout>
This approach saves me a lot of code! :)
As esteewhy explains, just swap the xml-defined contents into where you want them internally in your own layout, in onFinishInflate(). Example:
I take the contents that I specify in the xml:
<se.jog.custom.ui.Badge ... >
<ImageView ... />
<TextView ... />
</se.jog.custom.ui.Badge>
... and move them to my internal LinearLayout called contents where I want them to be:
public class Badge extends LinearLayout {
//...
private LinearLayout badge;
private LinearLayout contents;
// This way children can be added from xml.
#Override
protected void onFinishInflate() {
View[] children = detachChildren(); // gets and removes children from parent
//...
badge = (LinearLayout) layoutInflater.inflate(R.layout.badge, this);
contents = (LinearLayout) badge.findViewById(R.id.badge_contents);
for (int i = 0; i < children.length; i++)
addView(children[i]); //overridden, se below.
//...
super.onFinishInflate();
}
// This way children can be added from other code as well.
#Override
public void addView(View child) {
contents.addView(child);
}
Combined with custom XML attributes things gets very maintainable.
You can create your MyFancyLayout class by extending LinearLayout. Add the three constructors which call a method ("initialize" in this case) to set up the rest of the Views:
public MyFancyLayout(Context context) {
super(context);
initialize();
}
public MyFancyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public MyFancyLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
Within initialize, you do anything you need to to add the extra views. You can get the LayoutInflater and inflate another layout:
final LayoutInflater inflator = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflator.inflate(R.layout.somecommonlayout, this);
Or you can create Views in code and add them:
ImageView someImageView = new ImageView(getContext());
someImageView.setImageDrawable(myDrawable);
someImageView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
addView(someImageView);
If you're going to use the Context a lot, you can store a reference to it in your constructors and use that rather than getContext() to save a little overhead.
just use something like this:
<org.myprogram.MyFancyLayout>
...
</org.myprogram.MyFancyLayout>
Useful link - http://www.anddev.org/creating_custom_views_-_the_togglebutton-t310.html