How to Use Fragment Layouts in Android

How to Use Fragment Layouts in Android

android-293x300-123

Fragment is a concept of UI components-with a new idea for the ability to retain state across configuration changes. As a result, web-pages load comparatively faster because it retains their previous state. Without Fragments components, the normal activity class causes running activities to be stopped, reloaded and re-rendered using the new parameters. A fragment allows building a UI as a series of smaller, reusable graphical elements that can be arranged as needed, based on the device’s capabilities.

Fragment layout in Android is pretty distinct from other platforms. This design was first introduced for the platform in version 3.0 and onwards.

Here are the main concepts about Android fragment layout:

  • Android tabs are most often presented as text compared to icons, because it is difficult to come up with descriptive icons for all the possible navigation option. Text is much better.
  • Android tabs aren’t square buttons.  They mostly contain text
  • Visual style of Android tabs is flat. There should not be any glossy or reflection effects like in html web design.

A Fragment framework works much like an activity.
To implement it in the app we need an independent Java activity class along with a fragment xml layout:

  1. Create a layout XML and an Activity subclass for your activity
  2. Create a layout XML and a Fragment subclass for your fragment
  3. Map the two together in your Activity layout XML (or using FragmentTransaction mostly in Java code)

Example of layout xml for activity

[sourcecode]<?xml version="1.0" encoding="utf-8"?>
<TabHost
xmlns:android="http://schemas.android.com/apk/res/android"
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">

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

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

</LinearLayout>
</TabHost>
[/sourcecode]

Example of Activity subclass

[sourcecode]package com.myproj;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Stack;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TabHost;
import android.widget.TextView;

public class AppMainTabActivity extends FragmentActivity {
/* Your Tab host */
private TabHost mTabHost;

/* A HashMap of stacks, where we use tab identifier as keys..*/
private HashMap<String, Stack<Fragment>> mStacks;

/*Save current tabs identifier in this..*/
private String mCurrentTab;

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_layout);
/*
*  Navigation stacks for each tab gets created..
*  tab identifier is used as key to get respective stack for each tab
*/
mStacks = new HashMap<String, Stack<Fragment>>();
mStacks.put(AppConstants.TAB_A, new Stack<Fragment>());
mStacks.put(AppConstants.TAB_B, new Stack<Fragment>());
mStacks.put(AppConstants.TAB_C, new Stack<Fragment>());
mStacks.put(AppConstants.TAB_D, new Stack<Fragment>());
mTabHost = (TabHost)findViewById(android.R.id.tabhost);
mTabHost.setOnTabChangedListener(listener);
mTabHost.setup();
initializeTabs();
}

private View createTabView(final int id,String s) {
View view = LayoutInflater.from(this).inflate(R.layout.tabs_icon, null);
ImageView imageView =   (ImageView) view.findViewById(R.id.icon);
imageView.setImageDrawable(getResources().getDrawable(id));
TextView textview= (TextView) view.findViewById(R.id.title);
textview.setText(s);
return view;
}

public void initializeTabs(){
/* Setup your tab icons and content views.. Nothing special in this..*/
TabHost.TabSpec spec    =   mTabHost.newTabSpec(AppConstants.TAB_A);
mTabHost.setCurrentTab(-3);
spec.setContent(new TabHost.TabContentFactory() {
public View createTabContent(String tag) {
return findViewById(R.id.realtabcontent);
}
});
spec.setIndicator(createTabView(R.drawable.cameratab, "Camera"));
mTabHost.addTab(spec);

spec = mTabHost.newTabSpec(AppConstants.TAB_B);
spec.setContent(new TabHost.TabContentFactory() {
public View createTabContent(String tag) {
return findViewById(R.id.realtabcontent);
}
});
spec.setIndicator(createTabView(R.drawable.presettab, "Presets"));
mTabHost.addTab(spec);

//Create a class AppConstants to declare your variables

spec = mTabHost.newTabSpec(AppConstants.TAB_C);
spec.setContent(new TabHost.TabContentFactory() {
public View createTabContent(String tag) {
return findViewById(R.id.realtabcontent);
}
});
spec.setIndicator(createTabView(R.drawable.manualtab, "Manual Entry"));

mTabHost.addTab(spec);

spec = mTabHost.newTabSpec(AppConstants.TAB_D);
spec.setContent(new TabHost.TabContentFactory() {
public View createTabContent(String tag) {
return findViewById(R.id.realtabcontent);
}
});
spec.setIndicator(createTabView(R.drawable.infotab, "Info"));
mTabHost.addTab(spec);
}

/*Comes here when user switch tab, or we do programmatically*/
TabHost.OnTabChangeListener listener    =   new
TabHost.OnTabChangeListener() {
public void onTabChanged(String tabId) {
/*Set current tab..*/
mCurrentTab                     =   tabId;

if(mStacks.get(tabId).size() == 0){
/*
*    First time this tab is selected. So add first fragment of that tab.
*    Dont need animation, so that argument is false.
*    We are adding a new fragment which is not present in stack. So add to stack is true.
*/
if(tabId.equals(AppConstants.TAB_A)){
pushFragments(tabId, new Camera(), false,true);
}else if(tabId.equals(AppConstants.TAB_B)){
pushFragments(tabId, new PresetsActivity(), false,true);
}else if(tabId.equals(AppConstants.TAB_C)){
pushFragments(tabId, new ManualActivity(), false,true);
}else if(tabId.equals(AppConstants.TAB_D)){
pushFragments(tabId, new InfoActivity(), false,true);
}
}else {
/*
*    We are switching tabs, and target tab is already has atleast one fragment.
*    No need of animation, no need of stack pushing. Just show the target fragment
*/
pushFragments(tabId, mStacks.get(tabId).lastElement(), false,false);
}
}
};

/* Might be useful if we want to switch tab programmatically, from
inside any of the fragment.*/
public void setCurrentTab(int val){
mTabHost.setCurrentTab(val);
}

/*
*      To add fragment to a tab.
*  tag             ->  Tab identifier
*  fragment        ->  Fragment to show, in tab identified by tag
*  shouldAnimate   ->  should animate transaction. false when we switch tabs, or adding first fragment to a tab
*                      true when when we are pushing more fragment into navigation stack.
*  shouldAdd       ->  Should add to fragment navigation stack (mStacks.get(tag)). false when we are switching tabs (except for the first time)
*                      true in all other cases.
*/
public void pushFragments(String tag, Fragment fragment,boolean shouldAnimate, boolean shouldAdd){
if(shouldAdd)
mStacks.get(tag).push(fragment);
FragmentManager   manager         =   getSupportFragmentManager();
FragmentTransaction ft            =   manager.beginTransaction();
if(shouldAnimate)
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
ft.replace(R.id.realtabcontent, fragment);
ft.commit();
}

public void popFragments(){
/*
*    Select the second last fragment in current tab’s stack..
*    which will be shown after the fragment transaction given below
*/
Fragment fragment             =   mStacks.get(mCurrentTab).elementAt(mStacks.get(mCurrentTab).size() – 2);

/*pop current fragment from stack.. */
mStacks.get(mCurrentTab).pop();

/* We have the target fragment in hand.. Just show it.. Show a standard navigation animation*/
FragmentManager   manager         =   getSupportFragmentManager();
FragmentTransaction ft            =   manager.beginTransaction();
ft.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right);
ft.replace(R.id.realtabcontent, fragment);
ft.commit();
}

@SuppressLint("NewApi")
@Override
public void onBackPressed() {
if(((BaseFragment)mStacks.get(mCurrentTab).lastElement()).onBackPressed() == false){
/*
* top fragment in current tab doesn’t handles back press, we can do our thing, which is
*
* if current tab has only one fragment in stack, ie first fragment is showing for this tab.
*        finish the activity
* else
*        pop to previous fragment in stack for the same tab
*
*/
if(mStacks.get(mCurrentTab).size() == 1){
super.onBackPressed();  // or call finish..
}else{
popFragments();
}
}else{
//do nothing.. fragment already handled back button press.
}
}

/*
*   Imagine if you wanted to get an image selected using ImagePicker intent to the fragment. Ofcourse I could have created a public function
*  in that fragment, and called it from the activity. But couldn’t resist myself.
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {

mStacks.get(mCurrentTab).lastElement().onActivityResult(requestCode, resultCode, data);
}
}
[/sourcecode]

Example of Fragment subclass

[sourcecode]package com.myproj;

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;

public class BaseFragment extends Fragment {
public AppMainTabActivity mActivity;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

mActivity    = (AppMainTabActivity) this.getActivity();
}

public boolean onBackPressed(){
return false;
}
public void onActivityResult(int requestCode, int resultCode, Intent data){

}
}
[/sourcecode]

Fragment layout in Android is pretty distinct from other platforms. This design was first introduced for the platform in version 3.0 and onwards.

Here are the main concepts about Android fragment layout:

  1. Android tabs are most often presented as text compared to icons. Because, it is difficult to come up with descriptive icons for all the possible navigation option. Text is much better.
  2. Android tabs aren’t square buttons.  They mostly contain text
  3. Visual style of Android tabs is flat. There should not be any glossy or reflection effects like in html web design.

A fragment must always be embedded in an activity and the fragment’s lifecycle is directly affected by the parent activity’s lifecycle. When we add a fragment as a part of an activity layout, it lives in ViewGroup inside the activity’s view hierarchy and the fragment defines its own view layout. We can insert a fragment into the activity layout by declaring the fragment in the activity’s layout file, as a <fragment> element, or from the application code by adding it to an existing ViewGroup.
To animate the transition between fragments or to animate the process of showing or hiding a fragment the Fragment Manager API can be used and create a Fragment Transaction.
Within each Fragment Transaction we can specify in and out animations that will be used for show and hide respectively (or both when replace is used).

Benefits:

Fragments are useful in following cases:

  • If we split up views on some devices/orientations and show them in two activities and show all the content in one on other devices. That would be a use case if you go on a tablet or maybe even in landscape mode on a phone: e.g. you show the list of items and the details on one screen. On a phone or in portrait mode you just show one part.
  • Another use case is reusable views. So if we have some views that are visible on different activities and also perform some actions we could put this behavior into a fragment and then reuse it.
  • Animated effects can availed when dynamically adding and removing fragments from the screen
  • Integration with the action bar for tabs, as a replacement for TabHost
  • Integration with the action bar for “list”-based navigation (really a Spinner in the action bar, toggling between different fragments)
See Also: Webview Layouts usages in Android

If you have thoughts about or experiences with Android fragments, share them with us. We’d love to hear from you.

Tags:
Jay
Jayadev Das
jayadev.das@andolasoft.com

Do what you do best in – that’s what I’ve always believed in and that’s what I preach. Over the past 25+ years (yup that’s my expertise ‘n’ experience in the Information Technology domain), I’ve been consulting to small, medium and large companies ‘bout Web Technologies, Mobile Future as well as on the good-and-bad of tech. Blogger, International Business Advisor, Web Technology Expert, Sales Guru, Startup Mentor, Insurance Sales Portal Expert & a Tennis Player. And top of all – a complete family man!