Question

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;
  }
Was it helpful?

Solution

@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>

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top