[출처] http://android.codeandmagic.org/2011/07/android-tabs-with-fragments/



I’ve been recently using the Android Compatibility Library in a project where I  found myself in the need for a tabbed layout, in which each of the tab’s children would be a fragment.  The usual way to do this is for the activity you need the tabs in to extend TabActivity. However, if you want to use fragments in your activity, you must extend android.support.v4.app.FragmentActivity instead. Since Java doesn’t allow multiple class inheritance, you find yourself in a bit of dilemma.

Well, there’s a simple solution for making this work. What you need is basically to use a TabHost to hold both the TabWidget (which are the actual tab labels) and their content. Since the TabHost is nothing more than a View, you can place that anywhere, including in the root of a fragment defined in XML, like the one below (tabs_fragment.xml). I am hoping the comments in the code are self-explanatory enough.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0" encoding="utf-8"?>
<TabHost
    android:id="@android:id/tabhost"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#EFEFEF">
 
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
 
        <TabWidget
            android:id="@android:id/tabs"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
 
            <FrameLayout
                android:id="@android:id/tabcontent"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent">
 
                <FrameLayout
                    android:id="@+id/tab_1"
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent" />
 
                <FrameLayout
                    android:id="@+id/tab_2"
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent" />
 
            </FrameLayout>
    </LinearLayout>
</TabHost>

Don’t forget to use the correct ids (@android:id/tabhost@android:id/tabs and@android:id/tabcontent) because the TabHost expects exactly these in order to retrieve the references for each component of the view. If you want to use a custom style for your tabs, it is easy to do so by creating a tab_selector.xml in /res/drawables in which you can use different colors or backgrounds for each tab state. You can find an example in the attached zip.

The equivalent code for actually creating the tabs is in TabsFragment.java below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package org.codeandmagic.android;
 
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TabHost;
import android.widget.TabHost.OnTabChangeListener;
import android.widget.TabHost.TabSpec;
import android.widget.TextView;
 
public class TabsFragment extends Fragment implements OnTabChangeListener {
 
    private static final String TAG = "FragmentTabs";
    public static final String TAB_WORDS = "words";
    public static final String TAB_NUMBERS = "numbers";
 
    private View mRoot;
    private TabHost mTabHost;
    private int mCurrentTab;
 
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
    }
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        mRoot = inflater.inflate(R.layout.tabs_fragment, null);
        mTabHost = (TabHost) mRoot.findViewById(android.R.id.tabhost);
        setupTabs();
        return mRoot;
    }
 
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        setRetainInstance(true);
 
        mTabHost.setOnTabChangedListener(this);
        mTabHost.setCurrentTab(mCurrentTab);
        // manually start loading stuff in the first tab
        updateTab(TAB_WORDS, R.id.tab_1);
    }
 
    private void setupTabs() {
        mTabHost.setup(); // you must call this before adding your tabs!
        mTabHost.addTab(newTab(TAB_WORDS, R.string.tab_words, R.id.tab_1));
        mTabHost.addTab(newTab(TAB_NUMBERS, R.string.tab_numbers, R.id.tab_2));
    }
 
    private TabSpec newTab(String tag, int labelId, int tabContentId) {
        Log.d(TAG, "buildTab(): tag=" + tag);
 
        View indicator = LayoutInflater.from(getActivity()).inflate(
                R.layout.tab,
                (ViewGroup) mRoot.findViewById(android.R.id.tabs), false);
        ((TextView) indicator.findViewById(R.id.text)).setText(labelId);
 
        TabSpec tabSpec = mTabHost.newTabSpec(tag);
        tabSpec.setIndicator(indicator);
        tabSpec.setContent(tabContentId);
        return tabSpec;
    }
 
    @Override
    public void onTabChanged(String tabId) {
        Log.d(TAG, "onTabChanged(): tabId=" + tabId);
        if (TAB_WORDS.equals(tabId)) {
            updateTab(tabId, R.id.tab_1);
            mCurrentTab = 0;
            return;
        }
        if (TAB_NUMBERS.equals(tabId)) {
            updateTab(tabId, R.id.tab_2);
            mCurrentTab = 1;
            return;
        }
    }
 
    private void updateTab(String tabId, int placeholder) {
        FragmentManager fm = getFragmentManager();
        if (fm.findFragmentByTag(tabId) == null) {
            fm.beginTransaction()
                    .replace(placeholder, new MyListFragment(tabId), tabId)
                    .commit();
        }
    }
 
}

Now that you have the content of your fragment in place, you need to create the tab’s children (which are fragments, like the title says). You can subclass the  android.support.v4.app.Fragment (I used a ListFragment in this example). You can also use loaders if your fragment needs to do some background work. In the example below, I use an AsyncTaskLoader to simulate a time-consuming operation in background, at the end of which some words or numbers are added into the list’s adapter and displayed inside a tab. You also need a LoaderCallback object (or your fragment can directly implement this interface) in order to communicate with the LoaderManager to create your loader and also know when its job is done.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package org.codeandmagic.android;
 
import java.util.ArrayList;
import java.util.List;
 
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
 
public class MyListFragment extends ListFragment implements
        LoaderCallbacks<Void> {
 
    private static final String TAG = "FragmentTabs";
 
    private String mTag;
    private MyAdapter mAdapter;
    private ArrayList<String> mItems;
    private LayoutInflater mInflater;
    private int mTotal;
    private int mPosition;
 
    private static final String[] WORDS = { "Lorem", "ipsum", "dolor", "sit",
            "amet", "consectetur", "adipiscing", "elit", "Fusce", "pharetra",
            "luctus", "sodales" };
    private static final String[] NUMBERS = { "I", "II", "III", "IV", "V",
            "VI", "VII", "VIII", "IX", "X", "XI", "XII", "XIII", "XIV", "XV" };
 
    private static final int SLEEP = 1000;
 
    private final int wordBarColor = R.color.word_bar;
    private final int numberBarColor = R.color.number_bar;
 
    public MyListFragment() {
    }
 
    public MyListFragment(String tag) {
        mTag = tag;
        mTotal = TabsFragment.TAB_WORDS.equals(mTag) ? WORDS.length
                : NUMBERS.length;
 
        Log.d(TAG, "Constructor: tag=" + tag);
    }
 
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // this is really important in order to save the state across screen
        // configuration changes for example
        setRetainInstance(true);
 
        mInflater = LayoutInflater.from(getActivity());
 
        // you only need to instantiate these the first time your fragment is
        // created; then, the method above will do the rest
        if (mAdapter == null) {
            mItems = new ArrayList<String>();
            mAdapter = new MyAdapter(getActivity(), mItems);
        }
        getListView().setAdapter(mAdapter);
 
        // initiate the loader to do the background work
        getLoaderManager().initLoader(0, null, this);
    }
 
    @Override
    public Loader<Void> onCreateLoader(int id, Bundle args) {
        AsyncTaskLoader<Void> loader = new AsyncTaskLoader<Void>(getActivity()) {
 
            @Override
            public Void loadInBackground() {
                try {
                    // simulate some time consuming operation going on in the
                    // background
                    Thread.sleep(SLEEP);
                } catch (InterruptedException e) {
                }
                return null;
            }
        };
        // somehow the AsyncTaskLoader doesn't want to start its job without
        // calling this method
        loader.forceLoad();
        return loader;
    }
 
    @Override
    public void onLoadFinished(Loader<Void> loader, Void result) {
 
        // add the new item and let the adapter know in order to refresh the
        // views
        mItems.add(TabsFragment.TAB_WORDS.equals(mTag) ? WORDS[mPosition]
                : NUMBERS[mPosition]);
        mAdapter.notifyDataSetChanged();
 
        // advance in your list with one step
        mPosition++;
        if (mPosition < mTotal - 1) {
            getLoaderManager().restartLoader(0, null, this);
            Log.d(TAG, "onLoadFinished(): loading next...");
        } else {
            Log.d(TAG, "onLoadFinished(): done loading!");
        }
    }
 
    @Override
    public void onLoaderReset(Loader<Void> loader) {
    }
 
    private class MyAdapter extends ArrayAdapter<String> {
 
        public MyAdapter(Context context, List<String> objects) {
            super(context, R.layout.list_item, R.id.text, objects);
        }
 
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = convertView;
            Wrapper wrapper;
 
            if (view == null) {
                view = mInflater.inflate(R.layout.list_item, null);
                wrapper = new Wrapper(view);
                view.setTag(wrapper);
            } else {
                wrapper = (Wrapper) view.getTag();
            }
 
            wrapper.getTextView().setText(getItem(position));
            wrapper.getBar().setBackgroundColor(
                    mTag == TabsFragment.TAB_WORDS ? getResources().getColor(
                            wordBarColor) : getResources().getColor(
                            numberBarColor));
            return view;
        }
 
    }
 
    // use an wrapper (or view holder) object to limit calling the
    // findViewById() method, which parses the entire structure of your
    // XML in search for the ID of your view
    private class Wrapper {
        private final View mRoot;
        private TextView mText;
        private View mBar;
 
        public Wrapper(View root) {
            mRoot = root;
        }
 
        public TextView getTextView() {
            if (mText == null) {
                mText = (TextView) mRoot.findViewById(R.id.text);
            }
            return mText;
        }
 
        public View getBar() {
            if (mBar == null) {
                mBar = mRoot.findViewById(R.id.bar);
            }
            return mBar;
        }
    }
}

Now, all you need to do is create a FragmentActivity to accommodate your tabs fragment. You can either instantiate the fragment programmatically using the methods in the FragmentManager or by putting it directly in XML, like below (main.xml).

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
 
    <fragment
        class="org.codeandmagic.android.TabsFragment"
        android:id="@+id/tabs_fragment"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
</LinearLayout>

With that, your job is pretty much done. You can see  in the screenshot the result and also can download the project that’s available in the attached zip. Enjoy! :)



[출처] http://android.codeandmagic.org/2011/07/android-tabs-with-fragments/


+ Recent posts