Retroactively add EffectiveNavigation and TabCompat sample code.

This sample code was launched with the Implementing Effective Navigation and
Creating Backward-Compatible UIs training classes, but the source was never
added to AOSP. This retroactively adds the source, unmodified.

Change-Id: If6face5a0548107f7fd273e466b1ced2790f4f3a
This commit is contained in:
Roman Nurik
2012-08-10 14:48:22 -07:00
parent ef91cce81b
commit d80ee02011
49 changed files with 1517 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
/*
* Copyright 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.tabcompat;
import com.example.android.tabcompat.lib.CompatTab;
import com.example.android.tabcompat.lib.CompatTabListener;
import com.example.android.tabcompat.lib.TabCompatActivity;
import com.example.android.tabcompat.lib.TabHelper;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class MainActivity extends TabCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TabHelper tabHelper = getTabHelper();
CompatTab photosTab = tabHelper.newTab("photos")
.setText(R.string.tab_photos)
.setIcon(R.drawable.ic_tab_photos)
.setTabListener(new InstantiatingTabListener(this, PhotosFragment.class));
tabHelper.addTab(photosTab);
CompatTab videosTab = tabHelper.newTab("videos")
.setText(R.string.tab_videos)
.setIcon(R.drawable.ic_tab_videos)
.setTabListener(new InstantiatingTabListener(this, VideosFragment.class));
tabHelper.addTab(videosTab);
}
/**
* Implementation of {@link CompatTabListener} to handle tab change events. This implementation
* instantiates the specified fragment class with no arguments when its tab is selected.
*/
public static class InstantiatingTabListener implements CompatTabListener {
private final TabCompatActivity mActivity;
private final Class mClass;
/**
* Constructor used each time a new tab is created.
*
* @param activity The host Activity, used to instantiate the fragment
* @param cls The class representing the fragment to instantiate
*/
public InstantiatingTabListener(TabCompatActivity activity, Class<? extends Fragment> cls) {
mActivity = activity;
mClass = cls;
}
/* The following are each of the ActionBar.TabListener callbacks */
@Override
public void onTabSelected(CompatTab tab, FragmentTransaction ft) {
// Check if the fragment is already initialized
Fragment fragment = tab.getFragment();
if (fragment == null) {
// If not, instantiate and add it to the activity
fragment = Fragment.instantiate(mActivity, mClass.getName());
tab.setFragment(fragment);
ft.add(android.R.id.tabcontent, fragment, tab.getTag());
} else {
// If it exists, simply attach it in order to show it
ft.attach(fragment);
}
}
@Override
public void onTabUnselected(CompatTab tab, FragmentTransaction ft) {
Fragment fragment = tab.getFragment();
if (fragment != null) {
// Detach the fragment, because another one is being attached
ft.detach(fragment);
}
}
@Override
public void onTabReselected(CompatTab tab, FragmentTransaction ft) {
// User selected the already selected tab. Do nothing.
}
}
public static class PhotosFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
TextView textView = new TextView(getActivity());
textView.setGravity(Gravity.CENTER);
textView.setText(R.string.tab_photos);
return textView;
}
}
public static class VideosFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
TextView textView = new TextView(getActivity());
textView.setGravity(Gravity.CENTER);
textView.setText(R.string.tab_videos);
return textView;
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.tabcompat.lib;
import android.graphics.drawable.Drawable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
/**
* Represents a single tab.
* The {@link TabHelper} initializes one of the subclasses of this based
* on the current platform version, upon call to {@link TabHelper#newTab(String)}()
*/
public abstract class CompatTab {
final FragmentActivity mActivity;
final String mTag;
protected CompatTab(FragmentActivity activity, String tag) {
mActivity = activity;
mTag = tag;
}
public abstract CompatTab setText(int resId);
public abstract CompatTab setIcon(int resId);
public abstract CompatTab setTabListener(CompatTabListener callback);
public abstract CompatTab setFragment(Fragment fragment);
public abstract CharSequence getText();
public abstract Drawable getIcon();
public abstract CompatTabListener getCallback();
public abstract Fragment getFragment();
public abstract Object getTab();
public String getTag() {
return mTag;
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.tabcompat.lib;
import android.graphics.drawable.Drawable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
/**
* A base implementation of the {@link CompatTab} interface.
*/
public class CompatTabEclair extends CompatTab {
private CompatTabListener mCallback;
private CharSequence mText;
private Drawable mIcon;
private Fragment mFragment;
protected CompatTabEclair(FragmentActivity activity, String tag) {
super(activity, tag);
}
@Override
public CompatTab setText(int resId) {
mText = mActivity.getResources().getText(resId);
return this;
}
@Override
public CompatTab setIcon(int resId) {
mIcon = mActivity.getResources().getDrawable(resId);
return this;
}
@Override
public CompatTab setTabListener(CompatTabListener callback) {
mCallback = callback;
return this;
}
@Override
public CompatTab setFragment(Fragment fragment) {
mFragment = fragment;
return this;
}
@Override
public Fragment getFragment() {
return mFragment;
}
@Override
public CharSequence getText() {
return mText;
}
@Override
public Drawable getIcon() {
return mIcon;
}
@Override
public Object getTab() {
return null;
}
@Override
public CompatTabListener getCallback() {
return mCallback;
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.tabcompat.lib;
import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.graphics.drawable.Drawable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
/**
* An implementation of the {@link CompatTab} interface that relies on API 11 APIs.
*/
public class CompatTabHoneycomb extends CompatTab implements ActionBar.TabListener {
/**
* The native tab object that this {@link CompatTab} acts as a proxy for.
*/
ActionBar.Tab mTab;
CompatTabListener mCallback;
Fragment mFragment;
protected CompatTabHoneycomb(FragmentActivity activity, String tag) {
super(activity, tag);
mTab = activity.getActionBar().newTab();
}
@Override
public CompatTab setText(int resId) {
mTab.setText(resId);
return this;
}
@Override
public CompatTab setIcon(int resId) {
mTab.setIcon(resId);
return this;
}
@Override
public CompatTab setTabListener(CompatTabListener callback) {
mCallback = callback;
mTab.setTabListener(this);
return this;
}
@Override
public CharSequence getText() {
return mTab.getText();
}
@Override
public Drawable getIcon() {
return mTab.getIcon();
}
@Override
public Object getTab() {
return mTab;
}
@Override
public CompatTabListener getCallback() {
return mCallback;
}
@Override
public void onTabReselected(Tab tab, android.app.FragmentTransaction f) {
FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
ft.disallowAddToBackStack();
mCallback.onTabReselected(this, ft);
ft.commit();
}
@Override
public void onTabSelected(Tab tab, android.app.FragmentTransaction f) {
FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
ft.disallowAddToBackStack();
mCallback.onTabSelected(this, ft);
ft.commit();
}
@Override
public void onTabUnselected(Tab arg0, android.app.FragmentTransaction f) {
FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
ft.disallowAddToBackStack();
mCallback.onTabUnselected(this, ft);
ft.commit();
}
@Override
public CompatTab setFragment(Fragment fragment) {
mFragment = fragment;
return this;
}
@Override
public Fragment getFragment() {
return mFragment;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.tabcompat.lib;
import android.support.v4.app.FragmentTransaction;
/**
* @see android.app.ActionBar.TabListener
*/
public interface CompatTabListener {
/**
* @see android.app.ActionBar.TabListener#onTabSelected(
*android.app.ActionBar.Tab, android.app.FragmentTransaction)
*/
public void onTabSelected(CompatTab tab, FragmentTransaction ft);
/**
* @see android.app.ActionBar.TabListener#onTabUnselected(
*android.app.ActionBar.Tab, android.app.FragmentTransaction)
*/
public void onTabUnselected(CompatTab tab, FragmentTransaction ft);
/**
* @see android.app.ActionBar.TabListener#onTabReselected(
*android.app.ActionBar.Tab, android.app.FragmentTransaction)
*/
public void onTabReselected(CompatTab tab, FragmentTransaction ft);
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.tabcompat.lib;
import com.example.android.tabcompat.lib.TabHelper;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
/**
* A base activity that defers tab functionality to a {@link TabHelper}.
*
* When building an activity with tabs, extend this class in order to provide compatibility with API
* level 5 and above. Using this class along with the {@link TabHelper} and {@link com.example.android.tabcompat.lib.CompatTab}
* classes, you can build a tab UI that's built using the {@link android.app.ActionBar} on
* Honeycomb+ and the {@link android.widget.TabWidget} on all older versions.
*
* The {@link TabHelper} APIs obfuscate all the compatibility work for you.
*/
public abstract class TabCompatActivity extends FragmentActivity {
TabHelper mTabHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTabHelper = TabHelper.createInstance(this);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mTabHelper.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
mTabHelper.onRestoreInstanceState(savedInstanceState);
}
/**
* Returns the {@link TabHelper} for this activity.
*/
protected TabHelper getTabHelper() {
mTabHelper.setUp();
return mTabHelper;
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.tabcompat.lib;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
/**
* Convenience helper to build a set of tabs for a {@link TabCompatActivity}. To use this class,
* extend {@link TabCompatActivity} and:
*
* Call {@link TabCompatActivity#getTabHelper()}, returning a {@link TabHelper}.
*
* Create a {@link CompatTabListener}.
*
* Call {@link TabHelper#newTab(String)} to create each tab.
*
* Call CompatTab.setText().setIcon().setTabListener() to set up your tabs.
*
* Call {@link TabHelper#addTab(CompatTab)} for each tab, and you're done.
*/
public abstract class TabHelper {
protected FragmentActivity mActivity;
protected TabHelper(FragmentActivity activity) {
mActivity = activity;
}
/**
* Factory method for creating TabHelper objects for a given activity. Depending on which device
* the app is running, either a basic helper or Honeycomb-specific helper will be returned.
* Don't call this yourself; the TabCompatActivity instantiates one. Instead call
* TabCompatActivity.getTabHelper().
*/
public static TabHelper createInstance(FragmentActivity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
return new TabHelperHoneycomb(activity);
} else {
return new TabHelperEclair(activity);
}
}
/**
* Create a new tab.
*
* @param tag A unique tag to associate with the tab and associated fragment
* @return CompatTab for the appropriate android version
*/
public CompatTab newTab(String tag) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
return new CompatTabHoneycomb(mActivity, tag);
} else {
return new CompatTabEclair(mActivity, tag);
}
}
public abstract void addTab(CompatTab tab);
protected abstract void onSaveInstanceState(Bundle outState);
protected abstract void onRestoreInstanceState(Bundle savedInstanceState);
protected abstract void setUp();
}

View File

@@ -0,0 +1,156 @@
/*
* Copyright 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.tabcompat.lib;
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.TabSpec;
import java.util.HashMap;
/**
* This is a helper class to build tabs on pre-Honeycomb. Call {@link
* TabCompatActivity#getTabHelper()} to get the generic instance for
* compatibility with other versions.
*
* It implements a generic mechanism for associating fragments with the tabs in a tab host. 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 fragments. So instead we make the
* content part of the tab host 0dp high (it is not shown) and this supplies its own dummy view to
* show as the tab content. It listens to changes in tabs, then passes the event back to the tab's
* callback interface so the activity can take care of switching to the correct fragment.
*/
public class TabHelperEclair extends TabHelper implements TabHost.OnTabChangeListener {
private final HashMap<String, CompatTab> mTabs = new HashMap<String, CompatTab>();
private TabHost mTabHost;
CompatTabListener mCallback;
CompatTab mLastTab;
protected TabHelperEclair(FragmentActivity activity) {
super(activity);
mActivity = activity;
}
@Override
protected void setUp() {
if (mTabHost == null) {
mTabHost = (TabHost) mActivity.findViewById(android.R.id.tabhost);
mTabHost.setup();
mTabHost.setOnTabChangedListener(this);
}
}
@Override
public void addTab(CompatTab tab) {
String tag = tab.getTag();
TabSpec spec;
if (tab.getIcon() != null) {
spec = mTabHost.newTabSpec(tag).setIndicator(tab.getText(), tab.getIcon());
} else {
spec = mTabHost.newTabSpec(tag).setIndicator(tab.getText());
}
spec.setContent(new DummyTabFactory(mActivity));
// 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.
Fragment fragment = mActivity.getSupportFragmentManager().findFragmentByTag(tag);
tab.setFragment(fragment);
if (fragment != null && !fragment.isDetached()) {
FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
ft.detach(fragment);
ft.commit();
}
mTabs.put(tag, tab);
mTabHost.addTab(spec);
}
/**
* Converts the basic "tab changed" event for TabWidget into the three possible events for
* CompatTabListener: selected, unselected, reselected.
*/
@Override
public void onTabChanged(String tabId) {
CompatTab newTab = mTabs.get(tabId);
FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
if (mLastTab != newTab) {
if (mLastTab != null) {
if (mLastTab.getFragment() != null) {
// Pass the unselected event back to the tab's CompatTabListener
mLastTab.getCallback().onTabUnselected(mLastTab, ft);
}
}
if (newTab != null) {
// Pass the selected event back to the tab's CompatTabListener
newTab.getCallback().onTabSelected(newTab, ft);
}
mLastTab = newTab;
} else {
// Pass the re-selected event back to the tab's CompatTabListener
newTab.getCallback().onTabReselected(newTab, ft);
}
ft.commit();
mActivity.getSupportFragmentManager().executePendingTransactions();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
// Save and restore the selected tab for rotations/restarts.
outState.putString("tab", mTabHost.getCurrentTabTag());
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
if (savedInstanceState != null) {
mTabHost.setCurrentTabByTag(savedInstanceState.getString("tab"));
}
}
/**
* Backwards-compatibility mumbo jumbo
*/
static class DummyTabFactory implements TabHost.TabContentFactory {
private final Context mContext;
public DummyTabFactory(Context context) {
mContext = context;
}
@Override
public View createTabContent(String tag) {
View v = new View(mContext);
v.setMinimumWidth(0);
v.setMinimumHeight(0);
return v;
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.tabcompat.lib;
import android.app.ActionBar;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
/**
* Helper class to build tabs on Honeycomb. Call {@link TabCompatActivity#getTabHelper()}
* to get the generic instance for compatibility with older versions.
*/
public class TabHelperHoneycomb extends TabHelper {
ActionBar mActionBar;
protected TabHelperHoneycomb(FragmentActivity activity) {
super(activity);
}
@Override
protected void setUp() {
if (mActionBar == null) {
mActionBar = mActivity.getActionBar();
mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
}
}
@Override
public void addTab(CompatTab tab) {
String tag = tab.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.
Fragment fragment = mActivity.getSupportFragmentManager().findFragmentByTag(tag);
tab.setFragment(fragment);
if (fragment != null && !fragment.isDetached()) {
FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
ft.detach(fragment);
ft.commit();
}
if (tab.getCallback() == null) {
throw new IllegalStateException("CompatTab must have a CompatTabListener");
}
// We know tab is a CompatTabHoneycomb instance, so its
// native tab object is an ActionBar.Tab.
mActionBar.addTab((ActionBar.Tab) tab.getTab());
}
@Override
protected void onSaveInstanceState(Bundle outState) {
int position = mActionBar.getSelectedTab().getPosition();
outState.putInt("tab_position", position);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
int position = savedInstanceState.getInt("tab_position");
mActionBar.setSelectedNavigationItem(position);
}
}