Monday 18 February 2013

Nested Fragments

Previously we have learned some basics about using Fragments inside activtiy.

Fragments are very useful. However until recently fragments could not be used inside other fragment. That is you Activty can have multiple fragments ,but fragments cannot have fragments inside them.

With Android 4.2 APIv17 there was support for Nested Fragments Where you can use fragment inside other fragment. However there is a limitation that your fragment must be dynamic. There is no option to use <fragment> tags.

Here's a demo of using fragment inside fragment.

We will make use of our sample that we have been working in previous posts. We will change FragmentA. Here's a new Layout for FragmentA.
<?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" >
<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="I am FragmentA" />

<FrameLayout
        android:id="@+id/child_fragment"
        android:layout_width="fill_parent"
        android:layout_height="200dip" />

</LinearLayout>

We use FrameLayout wherever we want our fragment to be . And so the updated FragmentA will be

import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
 
public class FragmentA extends Fragment {
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
      View view = (View) inflater.inflate(R.layout."name of layout",
    container, false);
      return view ;
   }
}

Now the child Fragment let it be FragmentC
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
 
public class FragmentC extends Fragment {
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
      TextView textView=new TextView(getActivity());
      textView.setText("Hello I am fragment C");
      return textView;
   }
}
And to add it dynamically to your FragmentA
Fragment fragmentC = new FragmentC();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.add(R.id.child_fragment, fragmentC ).commit();
Use getParentFragment() to get the reference to parent similar to getActivity that gives reference of parent Activtiy.

Sunday 17 February 2013

Swipe Tabs with Fragments


In last chapter we had created Tabs with Fragments Now we will have swipe Gesture with our tabs. We still do not have swipe gestures yet.For this we will be using ViewPager You can think ViewPager roughly as a listView in horizontal.

Lets start by adding ViewPager to our layout for MainActivity,we be using code from previous post,


Now replace
<FrameLayout
            android:id="@+android:id/realtabcontent"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1" />

this with
<android.support.v4.view.ViewPager
                android:id="@+id/viewpager"
                android:layout_width="fill_parent"
                android:layout_height="0dp"
                android:layout_weight="1" />
This will make us replace
mTabManager = new TabManager(this, mTabHost, android.R.id.tabcontent);

Notice again here we are again using Androids default Tab content


Now we add ViewPager to our activity
this.mViewPager = (ViewPager) super.findViewById(R.id.viewpager);

ViewPager must have an Adapter similar to the way ListView does. We do not have any functionality to add so we ll keep it simple


public class PagerAdpater extends FragmentPagerAdapter {

 private List<Fragment> fragmentList;

 public PagerAdpater(FragmentManager fm, List<Fragment> fragmentList) {
  super(fm);
  this.fragmentList = fragmentList;
 }

 /*
  * (non-Javadoc)
  * 
  * @see android.support.v4.app.FragmentPagerAdapter#getItem(int)
  */
 @Override
 public Fragment getItem(int position) {
  // TODO Auto-generated method stub
  return fragmentList.get(position);
 }

 /*
  * (non-Javadoc)
  * 
  * @see android.support.v4.view.PagerAdapter#getCount()
  */
 @Override
 public int getCount() {
  // TODO Auto-generated method stub
  return fragmentList.size();
 }

}
The adapter must extend FragmentPagerAdapter

Our Adapter will also be managing change of Tabs and ViewPager's page so

 implements
  OnTabChangeListener, ViewPager.OnPageChangeListener {
  
 /**
  * Initialise ViewPager
  */
 private void initViewPager() {
  List<Fragment> fragments = new Vector<Fragment>();
  fragments
    .add(Fragment.instantiate(this, FragmentA.class.getName()));
  fragments
    .add(Fragment.instantiate(this, FragmentB.class.getName()));
  this.mPagerAdapter = new PagerAdpater(
    super.getSupportFragmentManager(), fragments);
  this.mViewPager = (ViewPager) super.findViewById(R.id.viewpager);
  this.mViewPager.setAdapter(this.mPagerAdapter);
  this.mViewPager.setOnPageChangeListener(this);
  Constants.log(TAG, "intialiseViewPager");
 }

Saturday 16 February 2013

Creating Tabs using Fragments

    Tabs are excellent way to provide user access to multiple functionality very easily. Tabs were earlier implemented  with multiple activity inside one activity however with Honeycomb(thats long time back) Android discourages use of Activity inside Activity.

    With HoneyComb Android Sdk introduced Fragment .Fragments are very powerful part of Android SDK. Fragment is UI component that provides better control.They provide more modular control of different functionality inside Activity.

     However to provide backwards compatibility you can use Compatibility Package .

The primary classes related to fragments are:


The base class for all activities using compatibility-based fragment (and loader) features

The base class for all fragment definitions

The class for interacting with fragment objects inside an activity

The class for performing an atomic set of fragment operations

Now lets start with our example,
We will create Application with two Tabs. So for each tab we will need two Fragment.

here is our sample fragment

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

public class FragmentA extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
      TextView textView=new TextView(getActivity());
      textView.setText("Hello I am fragment A");
      return textView;
   }
}

Similarly we can have FragmentB. Now we create our Activity that will be our tab container.For using Fragments inside Activity we must extend FragmentActivity.
import android.support.v4.app.FragmentActivity;
//Some more imports

public class MainActivity extends FragmentActivity {
      //More code to come later
Now its time for layout of our Activity


<tabhost  xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/tab_activity_bg" >


    <linearlayout 
 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

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



        <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="match_parent" 
           android:layout_height="0dp"
           android:layout_weight="1" />
           </tabwidget>
      </linearlayout>
</tabhost>
Please note the android:id it must be the same Now back to our Activity
    TabHost mTabHost;
    TabManager mTabManager;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTabHost = (TabHost) findViewById(android.R.id.tabhost);
        mTabHost.setup();
        mTabManager = new TabManager(this, mTabHost, R.id.realtabcontent);
        setTabView();

        mTabManager.addTab(
             mTabHost.newTabSpec("tabA").setIndicator(TabAText),
             FragmentA.class, null);
        mTabManager.addTab(
             mTabHost.newTabSpec("tabB").setIndicator(TabBText),
             FragmentB.class, null);



Here TabManager mTabManager is only thing that we will we discussing
TabManager will manage our tabs for us.



    
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.OnTabChangeListener;

public class TabManager implements OnTabChangeListener {

    private final FragmentActivity mActivity;
    private final TabHost mTabHost;
    private final int mContainerId;
    private final HashMap mTabs = new  
             HashMap();
    TabInfo mLastTab;

   static final class TabInfo {
       private final String tag;
       private final Class clss;
       private final Bundle args;
       private Fragment fragment;

       TabInfo(String _tag, Class _class, Bundle _args) {
           tag = _tag;
           clss = _class;
           args = _args;
       }

   }

  static class DummyTabFactory implements TabHost.TabContentFactory {
  private final Context mContext;

   public DummyTabFactory(Context context) {
       mContext = context;
   }

   public View createTabContent(String tag) {
       View v = new View(mContext);
       v.setMinimumWidth(0);
       v.setMinimumHeight(0);
       return v;
  }

 }

  public TabManager(FragmentActivity activity, TabHost tabHost,
   int containerId) {
      mActivity = activity;
      mTabHost = tabHost;
      mContainerId = containerId;
      mTabHost.setOnTabChangedListener(this);
 }

  public void addTab(TabHost.TabSpec tabSpec, Class clss, Bundle args) {
      tabSpec.setContent(new DummyTabFactory(mActivity));
      String tag = tabSpec.getTag();

      TabInfo info = new TabInfo(tag, clss, args);

  // 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.

      info.fragment = mActivity.getSupportFragmentManager().findFragmentByTag(tag);
      if (info.fragment != null && !info.fragment.isDetached()) {
          FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
          ft.detach(info.fragment);
          ft.commit();
      }

      mTabs.put(tag, info);
      mTabHost.addTab(tabSpec);
 }

  public void onTabChanged(String tabId) {
      TabInfo newTab = mTabs.get(tabId);
      if (mLastTab != newTab) {
      FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
      if (mLastTab != null) {
          if (mLastTab.fragment != null) {
              ft.detach(mLastTab.fragment);
          }
      }
      if (newTab != null) {
          if (newTab.fragment == null) {
              newTab.fragment = Fragment.instantiate(mActivity,newTab.clss.getName(),newTab.args);
              ft.add(mContainerId, newTab.fragment, newTab.tag);
          } else {
              ft.attach(newTab.fragment);
          }
      }
      mLastTab = newTab;
      ft.commit();
      mActivity.getSupportFragmentManager().executePendingTransactions();
  }
 }
}