RSS

Category Archives: Android

Android : Page Swiping using ViewPager

As a follow up on my last Fragments post, I thought it would be useful to include another Honeycomb feature which is the View Pager that implements for page swiping UI pattern.

In this example, I’ve re-used the Fragment implements from the Tabs post.

Requirements

To implement a Tabbed, using fragments on devices running Android 2.1 or higher, you’ll need to include the Android Compatibility library.  In my example, I’m using Compatibility library v4

Step-by-step

  1. Define the ViewPager layout
  2. Define the FragmentActivity container for the PageViewer
  3. Define the PagerAdapter

The Code

The ViewPager layout

The ViewPager layout (viewpager_layout.xml) simply declares the ViewPager class from the v4 compatibility library.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="vertical"
  >
  <android.support.v4.view.ViewPager
  	android:id="@+android:id/viewpager"
  	android:layout_width="fill_parent"
 	android:layout_height="fill_parent"
  	/>
</LinearLayout>

Defining the FragmentActivty

Our main FragmentActivity is going to host the ViewPager layout viewpager_layout.xml and initialise the ViewPager with an adapter that managers the fragments that are displayed when the user swipes between pages.  In my implementation, I simply instantiate the Fragments upfront and supply them in a list to the

constructor of the PagerAdapter PagerAdaptor.java.

As I mentioned in the intro, I reused the fragment implementations from my previous Fragment post where fragment 1 is Red, fragment 2 is Green and fragment 3 is Blue. After all, the design goal of Fragments is code/UI reuse.

/**
 *
 */
package com.andy.fragments.viewpager;

import java.util.List;
import java.util.Vector;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.ViewPager;

import com.andy.R;
import com.andy.fragments.tabs.Tab1Fragment;
import com.andy.fragments.tabs.Tab2Fragment;
import com.andy.fragments.tabs.Tab3Fragment;

/**
 * The <code>ViewPagerFragmentActivity</code> class is the fragment activity hosting the ViewPager
 * @author mwho
 */
public class ViewPagerFragmentActivity extends FragmentActivity{
	/** maintains the pager adapter*/
	private PagerAdapter mPagerAdapter;
	/* (non-Javadoc)
	 * @see android.support.v4.app.FragmentActivity#onCreate(android.os.Bundle)
	 */
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		super.setContentView(R.layout.viewpager_layout);
		//initialsie the pager
		this.initialisePaging();
	}

	/**
	 * Initialise the fragments to be paged
	 */
	private void initialisePaging() {

		List<Fragment> fragments = new Vector<Fragment>();
		fragments.add(Fragment.instantiate(this, Tab1Fragment.class.getName()));
		fragments.add(Fragment.instantiate(this, Tab2Fragment.class.getName()));
		fragments.add(Fragment.instantiate(this, Tab3Fragment.class.getName()));
		this.mPagerAdapter  = new PagerAdapter(super.getSupportFragmentManager(), fragments);
		//
		ViewPager pager = (ViewPager)super.findViewById(R.id.viewpager);
		pager.setAdapter(this.mPagerAdapter);
	}
}

Defining the PagerAdapter

The PagerAdapter class needs to extend FragmentPagerAdapter.  The most basic requirement for us, is to implement getItem(int position) and getCount().

/**
 *
 */
package com.andy.fragments.viewpager;

import java.util.List;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;

/**
 * The <code>PagerAdapter</code> serves the fragments when paging.
 * @author mwho
 */
public class PagerAdapter extends FragmentPagerAdapter {

	private List<Fragment> fragments;
	/**
	 * @param fm
	 * @param fragments
	 */
	public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
		super(fm);
		this.fragments = fragments;
	}
	/* (non-Javadoc)
	 * @see android.support.v4.app.FragmentPagerAdapter#getItem(int)
	 */
	@Override
	public Fragment getItem(int position) {
		return this.fragments.get(position);
	}

	/* (non-Javadoc)
	 * @see android.support.v4.view.PagerAdapter#getCount()
	 */
	@Override
	public int getCount() {
		return this.fragments.size();
	}
}

Here is what it looks like as we page from left-to-right:

 
75 Comments

Posted by on October 5, 2011 in Android

 

Tags: , , , ,

Android : Tabs. The Fragment Way

If you’re the die-hard follower (yes, I said “the” because the stats says there is) of this blog, you would have probably noticed that I’ve been M.I.A for awhile.  We’ll, it’s mainly been because I’ve been working on an Android project with a super-toit deadline which we managed to launch JIT on to the Marketplace.

Now that the first phase of that project is over, I’m now able to reflect a bit on the code that was produced and share here some of my experiences and (source).

In this first installment, I want to illustrate how to create a Tab activity using Fragments since, going-forward, it is suggested that building on Fragments will ensure your app is compatible with pre-Honeycomb, Honeycomb and Ice Cream Sandwich (tablet and phone) OS versions (see this article).  Remember, in Android 2.x Tabs are presented as classic “filing” tabs, while on Android 3.x and higher tabs are represented in the ActionBar UI component.

Requirements

To implement a Tabbed, using fragments on devices running Android 2.1 or higher, you’ll need to include the Android Compatibility library.  In my example, I’m using Compatibility library v4

Step-by-Step

  1. Define TabHost layout
  2. Define Tab fragment layouts
  3. Define Tab fragments
  4. Define main fragment activity

The Code

Define the TabHost layout

The tabbed UI layout consists of 4 parts: TabHost, TabWidget, FrameLayout and the content layout. tabs_layout.xml illustrates how they stack up.  You’ll notice that the FrameLayout id=realtabcontent is a child of a FrameLayout. Isn’t this redundant? The answer is no, be attaching out fragments to this FrameLayout.

<?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
	<TabHost
	    android:id="@android:id/tabhost"
	    android:layout_width="fill_parent"
	    android:layout_height="fill_parent"
	    >
	    <LinearLayout
	        android:orientation="vertical"
	        android:layout_width="fill_parent"
	        android:layout_height="fill_parent"
	        >

	        <TabWidget
	            android:id="@android:id/tabs"
	            android:orientation="horizontal"
	            android:layout_width="fill_parent"
	            android:layout_height="wrap_content"
	            android:layout_weight="0"
	            />

	        <FrameLayout
	            android:id="@android:id/tabcontent"
	            android:layout_width="0dp"
	            android:layout_height="0dp"
	            android:layout_weight="0"/>

	        <FrameLayout
	            android:id="@+android:id/realtabcontent"
	            android:layout_width="fill_parent"
	            android:layout_height="0dp"
	            android:layout_weight="1"/>
	    </LinearLayout>
	</TabHost>
</LinearLayout>

Define Tab fragment layouts

Next, we define out fragment layouts (i.e the content layout for each tab). Nothing special here…you’d define your layout as if the layout was for a stand-alone activity.  Below is tab_frag1_layout.xml (tab_frag2_layout.xml and tab_frag3_layout.xml are exactly the same except the have different colours specified for their backgrounds).

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:background="#FF0000"
  >
</LinearLayout>

Define Tab fragment classes

Each tab content fragment needs to extend Fragment and inflate it’s corresponding layout. As you’ll see later, each fragment is instantiated by the main FragmentActivity using the fragment manager.  Below is the definition of TabFragment1.java (TabFragment2.java and TabFragment3.java are exactly the same, except they inflate their respective layouts)

package com.andy.fragments.tabs;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;

import com.andy.R;

/**
 * @author mwho
 *
 */
public class Tab1Fragment extends Fragment {
	/** (non-Javadoc)
	 * @see android.support.v4.app.Fragment#onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle)
	 */
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		if (container == null) {
            // We have different layouts, and in one of them this
            // fragment's containing frame doesn't exist.  The fragment
            // may still be created from its saved state, but there is
            // no reason to try to create its view hierarchy because it
            // won't be displayed.  Note this is not needed -- we could
            // just run the code below, where we would create and return
            // the view hierarchy; it would just never be used.
            return null;
        }
		return (LinearLayout)inflater.inflate(R.layout.tab_frag1_layout, container, false);
	}
}

Define the main FragmentActivity

TabsFragmentActivity.java is where everything comes together.  Firstly, you’ll notice that TabsFragmentActivity extends FragementActivity instead of Activity.

Next, we look at onCreate(…). This is the starting point of our activity.  The first step is to inflate the tabbed layout as defined in tabs_layout.xml. In step 2, we initialise the tabs.  This involves invoking setup() on the TabHost view, adding tabs, save some extrinsic tab info (TabInfo) into a map and then setting the first tab content as active.

       /**
	 * Step 2: Setup TabHost
	 */
	private void initialiseTabHost(Bundle args) {
		mTabHost = (TabHost)findViewById(android.R.id.tabhost);
        mTabHost.setup();
        TabInfo tabInfo = null;
        TabsFragmentActivity.addTab(this, this.mTabHost, this.mTabHost.newTabSpec("Tab1").setIndicator("Tab 1"), ( tabInfo = new TabInfo("Tab1", Tab1Fragment.class, args)));
        this.mapTabInfo.put(tabInfo.tag, tabInfo);
        TabsFragmentActivity.addTab(this, this.mTabHost, this.mTabHost.newTabSpec("Tab2").setIndicator("Tab 2"), ( tabInfo = new TabInfo("Tab2", Tab2Fragment.class, args)));
        this.mapTabInfo.put(tabInfo.tag, tabInfo);
        TabsFragmentActivity.addTab(this, this.mTabHost, this.mTabHost.newTabSpec("Tab3").setIndicator("Tab 3"), ( tabInfo = new TabInfo("Tab3", Tab3Fragment.class, args)));
        this.mapTabInfo.put(tabInfo.tag, tabInfo);
        // Default to first tab
        this.onTabChanged("Tab1");
        //
        mTabHost.setOnTabChangedListener(this);
	}

I created a static helper method to add the tabs to the TabHost as defined by an instance of TabHost.TabSpec.  Each TabSpec is initialised with an instance of TabFactory (TabFactory is an inner class that extends TabContentFactory that creates an empty View as a placeholder for our fragments).  Next, we detach the Fragement associated with the tab we’re trying to add. Then, finally we add the TabSpec to the TabHost.

	private static void addTab(TabsFragmentActivity activity, TabHost tabHost, TabHost.TabSpec tabSpec, TabInfo tabInfo) {
		// Attach a Tab view factory to the spec
	tabSpec.setContent(activity.new TabFactory(activity));
        String tag = tabSpec.getTag();

        // Check to see if we already have a fragment for this tab, probably
        // from a previously saved state.  If so, deactivate it, because our
        // initial state is that a tab isn't shown.
        tabInfo.fragment = activity.getSupportFragmentManager().findFragmentByTag(tag);
        if (tabInfo.fragment != null && !tabInfo.fragment.isDetached()) {
            FragmentTransaction ft = activity.getSupportFragmentManager().beginTransaction();
            ft.detach(tabInfo.fragment);
            ft.commit();
            activity.getSupportFragmentManager().executePendingTransactions();
        }

        tabHost.addTab(tabSpec);
	}

Finally, we need to handle the onTabChanged(…) event handler where we’ll create, attach or detach the fragments when a specific tab is clicked.  When onTabChanged(…) is invoked a the tab’s tag value is passed as a parameter.  We use this tag value to look up the TabInfo instance we stored in initialiseTabHost(…), which has a reference to the fragment associated with the implied tab.

Our responsibility here is to detach the fragment for the tab we’re moving from, to the fragment for the tab that was clicked and to do nothing if the user clicked the active tab.

If the tab’s fragment doesn’t exist, it’s created using reflection on fragmentName.class on the FragmentManager by adding the fragment to the FrameLayout id=realcontent.

	public void onTabChanged(String tag) {
		TabInfo newTab = this.mapTabInfo.get(tag);
		if (mLastTab != newTab) {
			FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction();
            if (mLastTab != null) {
                if (mLastTab.fragment != null) {
                	ft.detach(mLastTab.fragment);
                }
            }
            if (newTab != null) {
                if (newTab.fragment == null) {
                    newTab.fragment = Fragment.instantiate(this,
                            newTab.clss.getName(), newTab.args);
                    ft.add(R.id.realtabcontent, newTab.fragment, newTab.tag);
                } else {
                    ft.attach(newTab.fragment);
                }
            }

            mLastTab = newTab;
            ft.commit();
            this.getSupportFragmentManager().executePendingTransactions();
		}
    }

Here’s the big picture

package com.andy.fragments.tabs;

import java.util.HashMap;

import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.view.View;
import android.widget.TabHost;
import android.widget.TabHost.TabContentFactory;

import com.andy.R;

/**
 * @author mwho
 *
 */
public class TabsFragmentActivity extends FragmentActivity implements TabHost.OnTabChangeListener {

	private TabHost mTabHost;
	private HashMap mapTabInfo = new HashMap();
	private TabInfo mLastTab = null;

	private class TabInfo {
		 private String tag;
         private Class clss;
         private Bundle args;
         private Fragment fragment;
         TabInfo(String tag, Class clazz, Bundle args) {
        	 this.tag = tag;
        	 this.clss = clazz;
        	 this.args = args;
         }

	}

	class TabFactory implements TabContentFactory {

		private final Context mContext;

	    /**
	     * @param context
	     */
	    public TabFactory(Context context) {
	        mContext = context;
	    }

	    /** (non-Javadoc)
	     * @see android.widget.TabHost.TabContentFactory#createTabContent(java.lang.String)
	     */
	    public View createTabContent(String tag) {
	        View v = new View(mContext);
	        v.setMinimumWidth(0);
	        v.setMinimumHeight(0);
	        return v;
	    }

	}
	/** (non-Javadoc)
	 * @see android.support.v4.app.FragmentActivity#onCreate(android.os.Bundle)
	 */
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		// Step 1: Inflate layout
		setContentView(R.layout.tabs_layout);
		// Step 2: Setup TabHost
		initialiseTabHost(savedInstanceState);
		if (savedInstanceState != null) {
            mTabHost.setCurrentTabByTag(savedInstanceState.getString("tab")); //set the tab as per the saved state
        }
	}

	/** (non-Javadoc)
     * @see android.support.v4.app.FragmentActivity#onSaveInstanceState(android.os.Bundle)
     */
    protected void onSaveInstanceState(Bundle outState) {
        outState.putString("tab", mTabHost.getCurrentTabTag()); //save the tab selected
        super.onSaveInstanceState(outState);
    }

	/**
	 * Step 2: Setup TabHost
	 */
	private void initialiseTabHost(Bundle args) {
		mTabHost = (TabHost)findViewById(android.R.id.tabhost);
        mTabHost.setup();
        TabInfo tabInfo = null;
        TabsFragmentActivity.addTab(this, this.mTabHost, this.mTabHost.newTabSpec("Tab1").setIndicator("Tab 1"), ( tabInfo = new TabInfo("Tab1", Tab1Fragment.class, args)));
        this.mapTabInfo.put(tabInfo.tag, tabInfo);
        TabsFragmentActivity.addTab(this, this.mTabHost, this.mTabHost.newTabSpec("Tab2").setIndicator("Tab 2"), ( tabInfo = new TabInfo("Tab2", Tab2Fragment.class, args)));
        this.mapTabInfo.put(tabInfo.tag, tabInfo);
        TabsFragmentActivity.addTab(this, this.mTabHost, this.mTabHost.newTabSpec("Tab3").setIndicator("Tab 3"), ( tabInfo = new TabInfo("Tab3", Tab3Fragment.class, args)));
        this.mapTabInfo.put(tabInfo.tag, tabInfo);
        // Default to first tab
        this.onTabChanged("Tab1");
        //
        mTabHost.setOnTabChangedListener(this);
	}

	/**
	 * @param activity
	 * @param tabHost
	 * @param tabSpec
	 * @param clss
	 * @param args
	 */
	private static void addTab(TabsFragmentActivity activity, TabHost tabHost, TabHost.TabSpec tabSpec, TabInfo tabInfo) {
		// Attach a Tab view factory to the spec
		tabSpec.setContent(activity.new TabFactory(activity));
        String tag = tabSpec.getTag();

        // Check to see if we already have a fragment for this tab, probably
        // from a previously saved state.  If so, deactivate it, because our
        // initial state is that a tab isn't shown.
        tabInfo.fragment = activity.getSupportFragmentManager().findFragmentByTag(tag);
        if (tabInfo.fragment != null && !tabInfo.fragment.isDetached()) {
            FragmentTransaction ft = activity.getSupportFragmentManager().beginTransaction();
            ft.detach(tabInfo.fragment);
            ft.commit();
            activity.getSupportFragmentManager().executePendingTransactions();
        }

        tabHost.addTab(tabSpec);
	}

	/** (non-Javadoc)
	 * @see android.widget.TabHost.OnTabChangeListener#onTabChanged(java.lang.String)
	 */
	public void onTabChanged(String tag) {
		TabInfo newTab = this.mapTabInfo.get(tag);
		if (mLastTab != newTab) {
			FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction();
            if (mLastTab != null) {
                if (mLastTab.fragment != null) {
                	ft.detach(mLastTab.fragment);
                }
            }
            if (newTab != null) {
                if (newTab.fragment == null) {
                    newTab.fragment = Fragment.instantiate(this,
                            newTab.clss.getName(), newTab.args);
                    ft.add(R.id.realtabcontent, newTab.fragment, newTab.tag);
                } else {
                    ft.attach(newTab.fragment);
                }
            }

            mLastTab = newTab;
            ft.commit();
            this.getSupportFragmentManager().executePendingTransactions();
		}
    }

}

The AndroidManifest.xml

Say something about AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.andy"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="8" />

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".fragments.tabs.TabsFragmentActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Summary

TODO

 
163 Comments

Posted by on October 4, 2011 in Android

 

Tags: , , , ,

 
Follow

Get every new post delivered to your Inbox.

Join 37 other followers