Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create reusable xml wrappers for Android Layout files

I have several layout files that are mostly the same, except for one section. Is there a way that I can have the common XML all in one place; instead of copy/pasting, and having to update a bunch of files when I want to make 1 change?

I know that I can include XML from other XML files, but the common code isn't an internal control; it is the outer wrapper; so include doesn't work. Basically, I have a bunch of files that all look like this:

<LinearLayout
    android:id="@+id/row"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal">

    <ImageView android:layout_height="26dp"
           android:id="@+id/checkImage"
           android:layout_width="26dp"
           android:layout_alignParentTop="true"
           android:scaleType="fitCenter"/>

    <!-- Different types of views go here depending on which layout file it is -->

    <ImageButton android:layout_height="fill_parent"
             android:id="@+id/playButton"
             android:layout_width="42dp"
             android:src="@drawable/play_button"
             android:scaleType="center"

             android:background="#00000000"/>

</LinearLayout>

Basically, I want to do what ASP.Net does with Master Pages. Is there any option for this?

like image 266
GendoIkari Avatar asked Jun 15 '11 18:06

GendoIkari


3 Answers

The solution was pretty easy.

You need to extend "Activity" Class, in onCreate() function SetContentView to your base xml layout and also need to override setContentView in base Activity Class

For Example:

1.Create base_layout.xml with the below code

<?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">
    <LinearLayout 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
           <ImageView 
               android:id="@+id/image_view_01"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:maxHeight="50dp" />
   </LinearLayout>

   <LinearLayout 
       android:id="@+id/base_layout"
       android:layout_width="match_parent"
       android:layout_height="match_parent" >
   </LinearLayout>
</LinearLayout>    
  1. Create BaseActivity.java
public class BaseActivity extends Activity {
    ImageView image;
    LinearLayout baseLayout;     

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); 
        super.setContentView(R.layout.base_layout);    

        this.image = (ImageView) this.findViewById(R.id.image_view_01);
        this.baseLayout = (LinearLayout) this.findViewById(R.id.base_layout);

        this.image.setImageResource(R.drawable.header);
    }

    @Override
    public void setContentView(int id) {
        LayoutInflater inflater = (LayoutInflater)getBaseContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(id, this.baseLayout);
    }
}

and SomeActivity.java

public class SomeActivity extends BaseActivity {

    @Override    
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.setContentView(R.layout.some_layout);

       //rest of code
    }
}

The only thing I noticed so far was that when requesting a progress bar (requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS)) this needs to be done before calling super.onCreate. I think this is because nothing can be drawn yet before calling this function.

This worked great for me and hopefully you will find this useful in your own coding.

like image 172
Ahmad Ronagh Avatar answered Oct 13 '22 00:10

Ahmad Ronagh


Maybe you could use one main layout XML file and then add/remove other widgets dynamically through code as needed.

like image 41
marchica Avatar answered Oct 13 '22 00:10

marchica


I was trying to do exactly this - I wanted a view that had a button on the left and a button on the right, but could have arbitrary content in the middle (depending on who was including it). Basically a custom view group that could have child view in the XML layout, and would wrap those child views with another XML layout. Here is how I did it:

top_bar.xml: This represents the common layout to wrap things with. Note the LinearLayout (could be any layout) with an ID "addChildrenHere" - it is referenced later.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/topBarLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="left" />

    <LinearLayout
        android:id="@+id/addChildrenHere"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"/>

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="right" />

</LinearLayout>

main.xml: The main layout. This includes a custom viewgroup (WrappedLayout) with a few children. Note how it declares a custom XML namespace, and sets two custom attributes on the WrappedLayout tag (these say which layout to wrap the children with, and where within that layout the children of this node should be placed).

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:karl="http://schemas.android.com/apk/res/karl.test"
    android:id="@+id/linearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <karl.test.WrappedLayout
        android:id="@+id/topBarLayout1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        karl:layoutToInflate="@layout/top_bar"
        karl:childContainerID="@+id/addChildrenHere">

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="This is a child of the special wrapper."
            android:textAppearance="?android:attr/textAppearanceMedium" />

        <TextView
            android:id="@+id/textView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="This is another child; you can put anything here."
            android:textAppearance="?android:attr/textAppearanceMedium" />

    </karl.test.WrappedLayout>

</LinearLayout>

attrs.xml: This goes in res/values. This defines the custom XML attributes used in the XML above.

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <declare-styleable name="WrappedLayout">
    <attr name="layoutToInflate" format="integer"/>
    <attr name="childContainerID" format="integer"/>
  </declare-styleable>
</resources>

Finally, WrappedLayout.java: This handles reading the custom attributes, and doing a bit of hackery to make addView() actually add the views in a different place.

package karl.test;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

public class WrappedLayout extends FrameLayout
{

  ///Attempts to add children to this layout will actually get forwarded through to mChildContainer.
  ///This would be final, but it's actually used indirectly by the constructor before it's initialised.
  private ViewGroup mChildContainer;

  public WrappedLayout(Context context, AttributeSet attrs)
  {
    super(context, attrs);

    //read the custom attributes
    final int layoutToInflate;
    final int childContainerID;
    {
      final TypedArray styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.WrappedLayout);

      layoutToInflate  = styledAttributes.getResourceId(R.styleable.WrappedLayout_layoutToInflate, 0);
      childContainerID = styledAttributes.getResourceId(R.styleable.WrappedLayout_childContainerID, 0);

      styledAttributes.recycle();
    }

    if(layoutToInflate == 0
    || childContainerID == 0)
    {
      Log.e("Error", "WrappedLayout.WrappedLayout(): Error reading custom attributes from XML. layoutToInflate = " + layoutToInflate + ", childContainerID =" + childContainerID);
    }
    else
    {
      //inflate the layout and (implicitly) add it as a child view
      final View inflatedLayout = View.inflate(context, layoutToInflate, this);

      //grab the reference to the container to pass children through to
      mChildContainer = (ViewGroup)inflatedLayout.findViewById(childContainerID);
    }
  }

  ///All the addView() overloads eventually call this method.
  @Override
  public void addView(View child, int index, ViewGroup.LayoutParams params)
  {
    if(mChildContainer == null)
    {
      //still inflating - we're adding one of the views that makes up the wrapper structure
      super.addView(child, index, params);
    }
    else
    {
      //finished inflating - forward the view through to the child container
      mChildContainer.addView(child, index, params);
    }
  }

}

This works, as far as I can tell. It doesn't work very well with the Eclipse layout editor (I'm not quite sure what the problem is), but you can view the layout fine. Changing the children of the WrappedLayout seems to require editing the XML manually.

like image 40
Karu Avatar answered Oct 13 '22 00:10

Karu