您的位置:首页 > 移动开发 > Android开发

Working with Fragments and ViewPager on Android

2014-05-10 07:13 671 查看
First of all, you should always read the docs, all the content you need to know you will find there. I know, there are sometimes we just want to see an example, grab the code and start
coding based on the example, but it might create bad habits on us, and sometimes lead us to wrong programming practices. So my advise is to follow the tutorial posts, but also read the documentations. 


Introduction

So, according to the documentation, a Fragment:

represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section
of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running.

So, you can think of a fragment as a subactivity, that you can reuse in different activities.

Remember that a fragment must always be embedded of an activity and that the fragment's lifecycle is directly affected by the host activity's lifecycle, for example when an Activity is paused, so are all fragments.

When you add a fragment as a part of an activity layout, it lives in a ViewGroup inside the activity's
view hierarchy and thefragment defines its own view layout. 


The Design Philosophy

Fragments were introduced on Android 3.0 (API Level 11), primarily to support more dynamic and flexible UI designs on large screens such as tablets. By dividing the layout of an activity into fragments, you become able to modify the activity's appearance at
runtime and preserve those changes in a back stack that's managed by the activity.

For example, a news application can use one fragment to show a list of articles on the left and another fragment to display an article on the right—both fragments appear in one activity, side by side, and each fragment has its
own set of lifecycle callback methods and handle their own user input events. Thus, instead of using one activity to select an article and another activity to read the article, the user can select an article and read it all within the same activity,
as illustrated in the tablet layout in figure 1.


 
Figure 1. An example of how two UI modules defined by fragments can be combined into one activity for a tablet design, but separated for a handset design.


The Sample Application

OK. Let's start our sample application, so you will learn how to implement it. Here we are just going implement a small application that will display a fragment with a ListView and a ViewPager with a fragment per page. It's pretty simple but will teach you
the concept. So the first step is to create your application. As we want to support older versions, we will have to use the support library, which you can download and learn how to set it up here.

Technical Observation: in this tutorial I'm using Android
Studio, but you can use whatever IDE you like.

For this application to support 2.2+ versions, you must set your application with minimum sdk version=8 and target sdk version=18 (in this case we want to reach the jelly bean version,
but use the target version you want), so in your
AndroidManifest.xml
 file,
you must have this:

<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="18" />



Create the Host Activity

Now we must create the host Activity, that will host both fragments. Create a class called 
MainActivity.java
.
This class must extend the FragmentActivity class from the support library.

package com.rogcg.fragmentssample;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.Menu;

public class MainActivity extends FragmentActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

}

@Override
public boolean onCreateOptionsMenu(Menu menu)
{
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

}


Also, we must create the layout for this Activity, that will hold a fragment element tag. So, create a layout for the activity called
activity_main.xml
.
Here is the code for the layout.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin">

<fragment android:name="com.rogcg.fragmentssample.FirstFragment"
android:id="@+id/firstFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF0000"
tools:layout="@layout/first_fragment" />

</LinearLayout>


As you can see, there is a 
<fragment>
 tag declared, representing a fragment,
also, it has it's own layout particularities. The
android:name
 attribute
in the 
<fragment>
 specifies the Fragment class to instantiate in the
layout. When the system creates this activity layout, it instantiates each fragment specified in the layout and calls the 
onCreateView()
 method
for each one, to retrieve each fragment's layout. The system inserts the View returned by the fragment directly in place of the 
<fragment>
element.


Creating the First Fragment

To create a Fragment you must extend the class Fragment (or an existing subclass of it). The Fragment
class has code that looks a lot like an Activity. It contains callback methods similar to an activity, such as onCreate(), onStart(), onPause(), and onStop(). In fact, if you're converting an existing Android application to use fragments, you might simply
move code from your activity's callback methods into the respective callback methods of your fragment.

There are some other subtypes that you can extend, like, DialogFragment(displays a floating dialog), PreferenceFragment(displays a hierarchy of Preference objects as a list, similar to
PreferenceActivity. This is useful when creating a "settings" activity for your application.) or ListFragment(displays a list of items that are managed by an adapter (such as a SimpleCursorAdapter), similar toListActivity).

In this case we are going to use ListFragment since we want to implement a ListView in our fragment.

So, create a class called 
FirstFragment.java
. Remember to extend the
ListFragment class from
android.support.v4.app.ListFragment
, or your
app won't support older versions. After that, we must implement the main methods.

package com.rogcg.fragmentssample;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;

import java.util.ArrayList;

public class FirstFragment extends ListFragment
{
private ArrayList<String> itemsArray = new ArrayList<String>();
private ArrayAdapter<String> adapter;

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);

itemsArray.add("Item 1");
itemsArray.add("Item 2");
itemsArray.add("Item 3");
itemsArray.add("Item 4");
itemsArray.add("Item 5");
itemsArray.add("Item 6");
itemsArray.add("Item 7");
itemsArray.add("Item 8");
itemsArray.add("Item 9");
itemsArray.add("Item 10");
itemsArray.add("Item 11");
itemsArray.add("Item 12");
itemsArray.add("Item 13");
itemsArray.add("Item 14");
itemsArray.add("Item 15");
itemsArray.add("Item 16");
itemsArray.add("Item 17");
itemsArray.add("Item 18");
itemsArray.add("Item 19");
itemsArray.add("Item 20");
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.inflate(R.layout.first_fragment, container, false);
}

@Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);

// Populates list with our static array
adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, itemsArray);
setListAdapter(adapter);
}

@Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
}

@Override
public void onStart()
{
super.onStart();
}

@Override
public void onResume()
{
super.onResume();
}
}


As you can see, the fragment life cycle looks a lot like an Activity. The onCreate(), onResume() methods are present, and we also have some specific methods for fragments like onCreateaView()onAttach().

OK, now lets create the layout for our FirstFragment. Create a xml layout file called 
first_fragment.xml
.
Here you can build whatever you want. Since the fragment has its own life cycle, you can add, remove and handle any UI element and data you want, that it will work separated from everything in the Activity (but remember that the
Fragment is attached to the Activity life cycle).

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/first_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="First Fragment"
android:id="@+id/firstFragmentTextView"
android:layout_gravity="center_horizontal|top" />

<ListView
android:id="@id/android:list"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</LinearLayout>


As we want to implement a ListView on this fragment, we must declare it in our layout. Also we are setting a TextView to be a title for the fragment.


Creating the ViewPager

OK, now we are going to work on the ViewPager, back to the MainActivity, we must implement something new there. Starting from the layout, we must add the TabHost, TabWidget, a FrameLayout and a ViewPager. So let's change the 
activity_main.xml
 a
little bit. Here is the end result.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:orientation="vertical">

<fragment android:name="com.rogcg.fragmentssample.FirstFragment"
android:id="@+id/firstFragment"
android:layout_width="match_parent"
android:layout_height="250dp"
android:background="#FF0000"
tools:layout="@layout/first_fragment" />

<TabHost
android:id="@android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent">

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

<TabWidget
android:id="@android:id/tabs"
android:orientation="horizontal"
android:layout_width="match_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"/>

<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>

</LinearLayout>
</TabHost>

</LinearLayout>


So, as you can see, the 
<fragment>
 tag is still there, and we created
something new. To create a ViewPager we must use theTabHost, which is a container for a tabbed
window view. This object holds two children: a set of tab labels that the user clicks to select a specific tab, and a FrameLayout object
that displays the contents of that page.

OK, now in our MainActivity code, let's add some attributes and declare our TabAdapter class. So here is the end result.

package com.rogcg.fragmentssample;

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.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TabHost;
import android.widget.TabWidget;

import java.util.ArrayList;

public class MainActivity extends FragmentActivity
{
private TabHost mTabHost;
private ViewPager mViewPager;
private TabsAdapter mTabsAdapter;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mTabHost = (TabHost)findViewById(android.R.id.tabhost);
mTabHost.setup();

mViewPager = (ViewPager)findViewById(R.id.pager);
mTabsAdapter = new TabsAdapter(this, mTabHost, mViewPager);

mTabsAdapter.addTab(mTabHost.newTabSpec("one").setIndicator("One"), PageOneFragment.class, null);
mTabsAdapter.addTab(mTabHost.newTabSpec("two").setIndicator("Two"), PageTwoFragment.class, null);

if (savedInstanceState != null)
{
mTabHost.setCurrentTabByTag(savedInstanceState.getString("tab"));
}
}

@Override
public boolean onCreateOptionsMenu(Menu menu)
{
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

public static class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener
{
private final Context mContext;
private final TabHost mTabHost;
private final ViewPager mViewPager;
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();

static final class TabInfo
{
private final String tag;
private final Class<?> clss;
private final Bundle args;

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 TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager pager)
{
super(activity.getSupportFragmentManager());
mContext = activity;
mTabHost = tabHost;
mViewPager = pager;
mTabHost.setOnTabChangedListener(this);
mViewPager.setAdapter(this);
mViewPager.setOnPageChangeListener(this);
}

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

TabInfo info = new TabInfo(tag, clss, args);
mTabs.add(info);
mTabHost.addTab(tabSpec);
notifyDataSetChanged();
}

@Override
public int getCount()
{
return mTabs.size();
}

@Override
public Fragment getItem(int position)
{
TabInfo info = mTabs.get(position);

return Fragment.instantiate(mContext, info.clss.getName(), info.args);

}

public void onTabChanged(String tabId)
{
int position = mTabHost.getCurrentTab();
mViewPager.setCurrentItem(position);
}

public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
{
}

public void onPageSelected(int position)
{
// Unfortunately when TabHost changes the current tab, it kindly
// also takes care of putting focus on it when not in touch mode.
// The jerk.
// This hack tries to prevent this from pulling focus out of our
// ViewPager.
TabWidget widget = mTabHost.getTabWidget();
int oldFocusability = widget.getDescendantFocusability();
widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
mTabHost.setCurrentTab(position);
widget.setDescendantFocusability(oldFocusability);
}

public void onPageScrollStateChanged(int state)
{
}
}
}


As you can see, there is a new class called TabsAdapter. This is a helper class that implements the management of tabs and all details of connecting a ViewPager with associated TabHost. It relies on a trick.

Normally a tab host has a simple API for supplying a View or Intent that each tab will show. This is not sufficient for switching between pages. So instead we make the content part of the tab host 0dp high (it is not shown) and the TabsAdapter supplies its
own dummy view to show as the tab content. It listens to changes in tabs, and takes care of switch to the correct paged in the ViewPager whenever the selected tab changes.

Also, as you have seen, we have added two more fragments, one for each page in the tabsAdapter by calling
mTabsAdapter.addTab
 in 
onCreate()
 method.
This will make sure, that in each tab, the correct fragments are loaded.


Creating two more fragments for the ViewPager

Now we are going to create the two fragments that are loaded in the ViewPager. Both are just the same, so, create two classes called 
PageOneFragment.java
 and 
PageTwoFragment.java
 respectively.

Here is the code for PageOneFragment.java.

package com.rogcg.fragmentssample;

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

public class PageOneFragment extends Fragment
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.inflate(R.layout.pageone_fragment, container, false);
}

@Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
}

@Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
}

@Override
public void onStart()
{
super.onStart();
}

@Override
public void onResume()
{
super.onResume();
}
}


As you can see, it inflates the layout pageone_fragment.xml, so let's create it.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/first_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:background="#ff4063ff">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="First Page"
android:textColor="#FFFFFF"
android:textStyle="bold"
android:id="@+id/firstFragmentTextView"
android:layout_gravity="center_horizontal|top" />

</LinearLayout>


Now, let's create the PageTwoFragment.java class. Here is the code.

package com.rogcg.fragmentssample;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class PageTwoFragment extends Fragment
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.inflate(R.layout.pagetwo_fragment, container, false);
}

@Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
}

@Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
}

@Override
public void onStart()
{
super.onStart();
}

@Override
public void onResume()
{
super.onResume();
}
}


And it's respective layout called pagetwo_fragment.xml.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/first_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:background="#ff656565">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Second Page"
android:textColor="#FFFFFF"
android:textStyle="bold"
android:id="@+id/firstFragmentTextView"
android:layout_gravity="center_horizontal|top" />

</LinearLayout>


As you can see, the only thing that differs between these fragments are the colors and the TextView text. This is because the main intention of this post is to show the concept and how to work with Fragments and ViewPager together, and not how to build a cool
UI.


The final result

So running the application on different versions, 2.2 (Nexus One) and 4.3 (Nexus 4), we have it working. As you can see the tabs on version 2.2 are very different from 4.3. This is because 2.2 doesn't have the Holo theme built in by default, and you have to
customize it and use which theme you prefer.

You can see that the elements on the page run independently from each other, and that you can add multiple fragments to an Activity, and build their structure different from other fragments. Also, you can add multiple fragments for a ViewPager by adding new
tabs. Pretty cool, huh?





source download address:https://github.com/rogcg/fragments-viewpager-sample
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ViewPager Fragment