/* Modifications to use add ability to switch the position and visibility of tabs
* and also to add replaceContent(Intent intent) method to TabSpec.
*
* Modifications Copyright (C) 2009 Justin Shapcott, nEx.Software
*
* 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.
*/
/* Original Copyright Information
*
* Copyright (C) 2006 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 my.android.widget;
import java.util.ArrayList;
import java.util.List;
import android.app.LocalActivityManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import be.benvd.mvforandroid.R;
/**
* 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. The individual elements are
* typically controlled using this container object, rather than setting values
* on the child elements themselves.
*/
public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchModeChangeListener
{
private TabWidget mTabWidget;
private FrameLayout mTabContent;
private List<TabSpec> mTabSpecs = new ArrayList<TabSpec>(2);
/**
* This field should be made private, so it is hidden from the SDK. {@hide
*
*
* }
*/
protected int mCurrentTab = -1;
private View mCurrentView = null;
/**
* This field should be made private, so it is hidden from the SDK. {@hide
*
*
* }
*/
protected LocalActivityManager mLocalActivityManager = null;
private OnTabChangeListener mOnTabChangeListener;
private OnKeyListener mTabKeyListener;
public TabHost(Context context)
{
super(context);
initTabHost();
}
public TabHost(Context context, AttributeSet attrs)
{
super(context, attrs);
initTabHost();
}
private final void initTabHost()
{
setFocusableInTouchMode(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
mCurrentTab = -1;
mCurrentView = null;
}
/**
* Get a new {@link TabSpec} associated with this tab host.
*
* @param tag
* required tag of tab.
*/
public TabSpec newTabSpec(String tag)
{
return new TabSpec(tag);
}
/**
* <p>
* Call setup() before adding tabs if loading TabHost using findViewById().
* <i><b>However</i></b>: You do not need to call setup() after getTabHost()
* in {@link my.android.app.TabActivity TabActivity}. Example:
* </p>
*
* <pre>
* mTabHost = (TabHost) findViewById(R.id.tabhost);
* mTabHost.setup();
* mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
*/
public void setup()
{
mTabWidget = (TabWidget) findViewById(android.R.id.tabs);
if(mTabWidget == null)
{
throw new RuntimeException("Your TabHost must have a TabWidget whose id attribute is 'android.R.id.tabs'");
}
// KeyListener to attach to all tabs. Detects non-navigation keys
// and relays them to the tab content.
mTabKeyListener = new OnKeyListener()
{
public boolean onKey(View v, int keyCode, KeyEvent event)
{
switch(keyCode)
{
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_DPAD_RIGHT:
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
case KeyEvent.KEYCODE_ENTER:
return false;
}
mTabContent.requestFocus(View.FOCUS_FORWARD);
return mTabContent.dispatchKeyEvent(event);
}
};
mTabWidget.setTabSelectionListener(new TabWidget.OnTabSelectionChanged()
{
public void onTabSelectionChanged(int tabIndex, boolean clicked)
{
setCurrentTab(tabIndex);
if(clicked)
{
mTabContent.requestFocus(View.FOCUS_FORWARD);
}
}
});
mTabContent = (FrameLayout) findViewById(android.R.id.tabcontent);
if(mTabContent == null)
{
throw new RuntimeException("Your TabHost must have a FrameLayout whose id attribute is 'android.R.id.tabcontent'");
}
}
/**
* If you are using {@link TabSpec#setContent(android.content.Intent)}, this
* must be called since the activityGroup is needed to launch the local
* activity.
*
* This is done for you if you extend {@link my.android.app.TabActivity}.
*
* @param activityGroup
* Used to launch activities for tab content.
*/
public void setup(LocalActivityManager activityGroup)
{
setup();
mLocalActivityManager = activityGroup;
}
@Override
protected void onAttachedToWindow()
{
super.onAttachedToWindow();
final ViewTreeObserver treeObserver = getViewTreeObserver();
if(treeObserver != null)
{
treeObserver.addOnTouchModeChangeListener(this);
}
}
@Override
protected void onDetachedFromWindow()
{
super.onDetachedFromWindow();
final ViewTreeObserver treeObserver = getViewTreeObserver();
if(treeObserver != null)
{
treeObserver.removeOnTouchModeChangeListener(this);
}
}
/**
* {@inheritDoc}
*/
public void onTouchModeChanged(boolean isInTouchMode)
{
if(!isInTouchMode)
{
// leaving touch mode.. if nothing has focus, let's give it to
// the indicator of the current tab
if(!mCurrentView.hasFocus() || mCurrentView.isFocused())
{
mTabWidget.getChildTabViewAt(mCurrentTab).requestFocus();
}
}
}
/**
* Add a tab.
*
* @param tabSpec
* Specifies how to create the indicator and content.
*/
public void addTab(TabSpec tabSpec)
{
if(tabSpec.mIndicatorStrategy == null)
{
throw new IllegalArgumentException("you must specify a way to create the tab indicator.");
}
if(tabSpec.mContentStrategy == null)
{
throw new IllegalArgumentException("you must specify a way to create the tab content");
}
View tabIndicator = tabSpec.mIndicatorStrategy.createIndicatorView();
tabIndicator.setOnKeyListener(mTabKeyListener);
// If this is a custom view, then do not draw the bottom strips for
// the tab indicators.
if(tabSpec.mIndicatorStrategy instanceof ViewIndicatorStrategy)
{
mTabWidget.setDrawBottomStrips(false);
}
mTabWidget.addView(tabIndicator);
mTabSpecs.add(tabSpec);
if(mCurrentTab == -1)
{
setCurrentTab(0);
}
}
/**
* Removes all tabs from the tab widget associated with this tab host.
*/
public void clearAllTabs()
{
mTabWidget.removeAllViews();
initTabHost();
mTabContent.removeAllViews();
mTabSpecs.clear();
requestLayout();
invalidate();
}
public TabWidget getTabWidget()
{
return mTabWidget;
}
public int getCurrentTab()
{
return mCurrentTab;
}
public String getCurrentTabTag()
{
if(mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size())
{
return mTabSpecs.get(mCurrentTab).getTag();
}
return null;
}
public View getCurrentTabView()
{
if(mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size())
{
return mTabWidget.getChildTabViewAt(mCurrentTab);
}
return null;
}
public View getCurrentView()
{
return mCurrentView;
}
public void setCurrentTabByTag(String tag)
{
int i;
for(i = 0; i < mTabSpecs.size(); i++)
{
if(mTabSpecs.get(i).getTag().equals(tag))
{
setCurrentTab(i);
break;
}
}
}
/**
* Get the FrameLayout which holds tab content
*/
public FrameLayout getTabContentView()
{
return mTabContent;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event)
{
final boolean handled = super.dispatchKeyEvent(event);
// unhandled key ups change focus to tab indicator for embedded
// activities
// when there is nothing that will take focus from default focus
// searching
if(!handled && (event.getAction() == KeyEvent.ACTION_DOWN) && (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP)
/* && (mCurrentView.isRootNamespace()) */
&& (mCurrentView.hasFocus()) && (mCurrentView.findFocus().focusSearch(View.FOCUS_UP) == null))
{
mTabWidget.getChildTabViewAt(mCurrentTab).requestFocus();
playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
return true;
}
return handled;
}
@Override
public void dispatchWindowFocusChanged(boolean hasFocus)
{
mCurrentView.dispatchWindowFocusChanged(hasFocus);
}
public void setCurrentTab(int index)
{
if(index < 0 || index >= mTabSpecs.size())
{
return;
}
if(index == mCurrentTab)
{
return;
}
// notify old tab content
if(mCurrentTab != -1)
{
mTabSpecs.get(mCurrentTab).mContentStrategy.tabClosed();
}
mCurrentTab = index;
final TabHost.TabSpec spec = mTabSpecs.get(index);
// Call the tab widget's focusCurrentTab(), instead of just
// selecting the tab.
mTabWidget.focusCurrentTab(mCurrentTab);
// tab content
mCurrentView = spec.mContentStrategy.getContentView();
if(mCurrentView.getParent() == null)
{
mTabContent.addView(mCurrentView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
}
if(!mTabWidget.hasFocus())
{
// if the tab widget didn't take focus (likely because we're in
// touch mode)
// give the current tab content view a shot
mCurrentView.requestFocus();
}
// mTabContent.requestFocus(View.FOCUS_FORWARD);
invokeOnTabChangeListener();
}
/**
* Register a callback to be invoked when the selected state of any of the
* items in this list changes
*
* @param l
* The callback that will run
*/
public void setOnTabChangedListener(OnTabChangeListener l)
{
mOnTabChangeListener = l;
}
private void invokeOnTabChangeListener()
{
if(this.getTabContentView() instanceof Switcher)
{
((Switcher) this.getTabContentView()).setCurrentIndex(this.getCurrentTab());
}
if(mOnTabChangeListener != null)
{
mOnTabChangeListener.onTabChanged(getCurrentTabTag());
}
}
public void moveTabsToBottom()
{
TabWidget MyTabWidget = this.getTabWidget();
LinearLayout MyTabWidgetParent = (LinearLayout) MyTabWidget.getParent();
MyTabWidgetParent.removeView(MyTabWidget);
MyTabWidgetParent.addView(MyTabWidget);
}
public void moveTabsToTop()
{
TabWidget MyTabWidget = this.getTabWidget();
LinearLayout MyTabWidgetParent = (LinearLayout) MyTabWidget.getParent();
MyTabWidgetParent.removeView(MyTabWidget);
MyTabWidgetParent.addView(MyTabWidget, 0);
}
public void hideTabs()
{
TabWidget MyTabWidget = this.getTabWidget();
MyTabWidget.setVisibility(View.GONE);
}
public void showTabs()
{
TabWidget MyTabWidget = this.getTabWidget();
MyTabWidget.setVisibility(View.VISIBLE);
}
/**
* Interface definition for a callback to be invoked when tab changed
*/
public interface OnTabChangeListener
{
void onTabChanged(String tabId);
}
/**
* Makes the content of a tab when it is selected. Use this if your tab
* content needs to be created on demand, i.e. you are not showing an
* existing view or starting an activity.
*/
public interface TabContentFactory
{
/**
* Callback to make the tab contents
*
* @param tag
* Which tab was selected.
* @return The view to display the contents of the selected tab.
*/
View createTabContent(String tag);
}
/**
* A tab has a tab indicator, content, and a tag that is used to keep track
* of it. This builder helps choose among these options.
*
* For the tab indicator, your choices are: 1) set a label 2) set a label
* and an icon
*
* For the tab content, your choices are: 1) the id of a {@link View} 2) a
* {@link TabContentFactory} that creates the {@link View} content. 3) an
* {@link Intent} that launches an {@link my.android.app.Activity}.
*/
public class TabSpec
{
private String mTag;
private IndicatorStrategy mIndicatorStrategy;
private ContentStrategy mContentStrategy;
private TabSpec(String tag)
{
mTag = tag;
}
/**
* Specify a label as the tab indicator.
*/
public TabSpec setIndicator(CharSequence label)
{
mIndicatorStrategy = new LabelIndicatorStrategy(label);
return this;
}
/**
* Specify a label and icon as the tab indicator.
*/
public TabSpec setIndicator(CharSequence label, Drawable icon)
{
mIndicatorStrategy = new LabelAndIconIndicatorStrategy(label, icon);
return this;
}
/**
* Specify a view as the tab indicator.
*/
public TabSpec setIndicator(View view)
{
mIndicatorStrategy = new ViewIndicatorStrategy(view);
return this;
}
/**
* Specify the id of the view that should be used as the content of the
* tab.
*/
public TabSpec setContent(int viewId)
{
mContentStrategy = new ViewIdContentStrategy(viewId);
return this;
}
/**
* Specify a {@link my.android.widget.TabHost.TabContentFactory} to use
* to create the content of the tab.
*/
public TabSpec setContent(TabContentFactory contentFactory)
{
mContentStrategy = new FactoryContentStrategy(mTag, contentFactory);
return this;
}
/**
* Specify an intent to use to launch an activity as the tab content.
*/
public TabSpec setContent(Intent intent)
{
mContentStrategy = new IntentContentStrategy(mTag, intent);
return this;
}
public String getTag()
{
return mTag;
}
// Added this as a convenient way to replace the content of the TabSpec
// when content is an Activity.
// Tell the tab to close, destroy the underlying activity so it isn't in
// limbo, then recreate the tab
// This could be done for other content types but for the purpose of
// this tutorial, I am only doing this.
public TabSpec replaceContent(Intent intent)
{
mContentStrategy.tabClosed();
mLocalActivityManager.destroyActivity(this.getTag(), true);
mContentStrategy = new IntentContentStrategy(mTag, intent);
mCurrentView = mContentStrategy.getContentView();
if(mCurrentView.getParent() == null)
{
mTabContent.addView(mCurrentView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
}
return this;
}
}
/**
* Specifies what you do to create a tab indicator.
*/
private static interface IndicatorStrategy
{
/**
* Return the view for the indicator.
*/
View createIndicatorView();
}
/**
* Specifies what you do to manage the tab content.
*/
private static interface ContentStrategy
{
/**
* Return the content view. The view should may be cached locally.
*/
View getContentView();
/**
* Perhaps do something when the tab associated with this content has
* been closed (i.e make it invisible, or remove it).
*/
void tabClosed();
}
/**
* How to create a tab indicator that just has a label.
*/
private class LabelIndicatorStrategy implements IndicatorStrategy
{
private final CharSequence mLabel;
private LabelIndicatorStrategy(CharSequence label)
{
mLabel = label;
}
public View createIndicatorView()
{
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View tabIndicator = inflater.inflate(R.layout.tab_indicator, mTabWidget, // tab
// widget
// is
// the
// parent
false); // no inflate params
final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);
tv.setText(mLabel);
return tabIndicator;
}
}
/**
* How we create a tab indicator that has a label and an icon
*/
private class LabelAndIconIndicatorStrategy implements IndicatorStrategy
{
private final CharSequence mLabel;
private final Drawable mIcon;
private LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon)
{
mLabel = label;
mIcon = icon;
}
public View createIndicatorView()
{
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View tabIndicator = inflater.inflate(R.layout.tab_indicator, mTabWidget, // tab
// widget
// is
// the
// parent
false); // no inflate params
final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);
tv.setText(mLabel);
final ImageView iconView = (ImageView) tabIndicator.findViewById(R.id.icon);
iconView.setImageDrawable(mIcon);
return tabIndicator;
}
}
/**
* How to create a tab indicator by specifying a view.
*/
private class ViewIndicatorStrategy implements IndicatorStrategy
{
private final View mView;
private ViewIndicatorStrategy(View view)
{
mView = view;
}
public View createIndicatorView()
{
return mView;
}
}
/**
* How to create the tab content via a view id.
*/
private class ViewIdContentStrategy implements ContentStrategy
{
private final View mView;
private ViewIdContentStrategy(int viewId)
{
mView = mTabContent.findViewById(viewId);
if(mView != null)
{
mView.setVisibility(View.GONE);
}
else
{
throw new RuntimeException("Could not create tab content because " + "could not find view with id " + viewId);
}
}
public View getContentView()
{
mView.setVisibility(View.VISIBLE);
return mView;
}
public void tabClosed()
{
mView.setVisibility(View.GONE);
}
}
/**
* How tab content is managed using {@link TabContentFactory}.
*/
private class FactoryContentStrategy implements ContentStrategy
{
private View mTabContent;
private final CharSequence mTag;
private TabContentFactory mFactory;
public FactoryContentStrategy(CharSequence tag, TabContentFactory factory)
{
mTag = tag;
mFactory = factory;
}
public View getContentView()
{
if(mTabContent == null)
{
mTabContent = mFactory.createTabContent(mTag.toString());
}
mTabContent.setVisibility(View.VISIBLE);
return mTabContent;
}
public void tabClosed()
{
mTabContent.setVisibility(View.INVISIBLE);
}
}
/**
* How tab content is managed via an {@link Intent}: the content view is the
* decorview of the launched activity.
*/
private class IntentContentStrategy implements ContentStrategy
{
private final String mTag;
private final Intent mIntent;
private View mLaunchedView;
private IntentContentStrategy(String tag, Intent intent)
{
mTag = tag;
mIntent = intent;
}
public View getContentView()
{
if(mLocalActivityManager == null)
{
throw new IllegalStateException("Did you forget to call 'public void setup(LocalActivityManager activityGroup)'?");
}
final Window w = mLocalActivityManager.startActivity(mTag, mIntent);
final View wd = w != null ? w.getDecorView() : null;
if(mLaunchedView != wd && mLaunchedView != null)
{
if(mLaunchedView.getParent() != null)
{
mTabContent.removeView(mLaunchedView);
}
}
mLaunchedView = wd;
if(mLaunchedView != null)
{
mLaunchedView.setVisibility(View.VISIBLE);
mLaunchedView.setFocusableInTouchMode(true);
((ViewGroup) mLaunchedView).setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
}
return mLaunchedView;
}
public void tabClosed()
{
if(mLaunchedView != null)
{
mLaunchedView.setVisibility(View.GONE);
}
}
}
}