/* * Copyright (C) 2005-2009 Team XBMC * http://xbmc.org * * This Program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This Program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with XBMC Remote; see the file license. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ package org.xbmc.android.widget.slidingtabs; import java.util.ArrayList; import java.util.List; import org.xbmc.android.remote.R; import android.app.LocalActivityManager; import android.content.Context; import android.content.Intent; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.Window; import android.widget.FrameLayout; /** * 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 SlidingTabHost extends FrameLayout implements ViewTreeObserver.OnTouchModeChangeListener { private SlidingTabWidget mTabWidget; private FrameLayout mTabContent; private List<SlidingTabSpec> mTabSpecs = new ArrayList<SlidingTabSpec>(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; public SlidingTabHost(Context context) { super(context); initTabHost(); } public SlidingTabHost(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 SlidingTabSpec} associated with this tab host. * * @param tag * required tag of tab. */ public SlidingTabSpec newTabSpec(String tag, String label, int resActiveIcon, int resInactiveIcon) { return new SlidingTabSpec(tag, label, resActiveIcon, resInactiveIcon); } /** * <p> * Call setup() before adding tabs if loading SlidingTabHost using * findViewById(). <i><b>However</i></b>: You do not need to call setup() * after getTabHost() in {@link android.app.TabActivity TabActivity}. * Example: * </p> * * <pre> * mTabHost = (SlidingTabHost) findViewById(R.id.tabhost); * mTabHost.setup(); * mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); */ public void setup() { mTabWidget = (SlidingTabWidget) findViewById(R.id.slidingtabs); if (mTabWidget == null) { throw new RuntimeException("Your SlidingTabHost must have a SlidingTabWidget whose id attribute is 'R.id.slidingtabs'"); } mTabWidget.setTabSelectionListener(new SlidingTabWidget.OnTabSelectionChanged() { public void onTabSelectionChanged(int tabIndex, boolean clicked) { setCurrentTab(tabIndex); if (clicked) { mTabContent.requestFocus(View.FOCUS_FORWARD); } } }); mTabContent = (FrameLayout) findViewById(R.id.slidingtabcontent); if (mTabContent == null) { throw new RuntimeException("Your SlidingTabHost must have a FrameLayout whose id attribute is 'android.R.id.tabcontent'"); } mTabWidget.setTabContent(mTabContent); } /** * If you are using {@link SlidingTabSpec#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 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(SlidingTabSpec tabSpec) { // LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); // View tabIndicator = inflater.inflate(R.layout.tab_indicator, mTabWidget, false); if (tabSpec.mContentStrategy == null) { throw new IllegalArgumentException("you must specify a way to create the tab content"); } mTabWidget.addTab(tabSpec); //.setOnKeyListener(mTabKeyListener); // mTabWidget.updateOnKeyListener(mTabKeyListener); mTabSpecs.add(tabSpec); if (mCurrentTab == -1) { setCurrentTab(0); } } /* * Moves the slider to the given tab and switches the content */ public void selectTabByTag(String tag) { int i; for (i = 0; i < mTabSpecs.size(); i++) { if (mTabSpecs.get(i).getTag().equals(tag)) { setCurrentTab(i); mTabWidget.moveTo(i); break; } } } /** * 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 SlidingTabWidget 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) { switch (event.getKeyCode()) { case KeyEvent.KEYCODE_DPAD_LEFT: if (event.getAction() == KeyEvent.ACTION_DOWN) { final int tabIndex = mCurrentTab - 1; if (tabIndex >= 0) { setCurrentTab(tabIndex); mTabWidget.moveTo(tabIndex); } } return true; case KeyEvent.KEYCODE_DPAD_RIGHT: if (event.getAction() == KeyEvent.ACTION_DOWN) { final int tabIndex = mCurrentTab + 1; if (tabIndex <= mTabWidget.getTabCount()) { setCurrentTab(tabIndex); mTabWidget.moveTo(tabIndex); } } return true; default: return super.dispatchKeyEvent(event); } } @Override public void dispatchWindowFocusChanged(boolean hasFocus) { View v = mCurrentView; if (v != null) { 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 SlidingTabHost.SlidingTabSpec 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 (mOnTabChangeListener != null) { mOnTabChangeListener.onTabChanged(getCurrentTabTag()); } } /** * 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 sliding tab has an active icon, a passive icon, a big icon and a text.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 android.app.Activity}. */ public class SlidingTabSpec { private String mTag; private int mBigIcon = -1; private final int mActiveIcon; private final int mInactiveIcon; private final String mLabel; private ContentStrategy mContentStrategy; private SlidingTabSpec(String tag, String label, int resActiveIcon, int resInactiveIcon) { mTag = tag; mLabel = label; mActiveIcon = resActiveIcon; mInactiveIcon = resInactiveIcon; } /** * Sets the big icon display in the content zone while sliding. */ public SlidingTabSpec setBigIcon(int resBigIcon) { mBigIcon = resBigIcon; return this; } /** * Specify the id of the view that should be used as the content of the * tab. */ public SlidingTabSpec setContent(int viewId) { mContentStrategy = new ViewIdContentStrategy(viewId); return this; } /** * Specify a {@link android.widget.TabHost.TabContentFactory} to use to * create the content of the tab. */ public SlidingTabSpec setContent(TabContentFactory contentFactory) { mContentStrategy = new FactoryContentStrategy(mTag, contentFactory); return this; } /** * Specify an intent to use to launch an activity as the tab content. */ public SlidingTabSpec setContent(Intent intent) { mContentStrategy = new IntentContentStrategy(mTag, intent); return this; } public String getTag() { return mTag; } public int getActiveIconResource() { return mActiveIcon; } public int getInactiveIconResource() { return mInactiveIcon; } public String getLabel() { return mLabel; } public int getBigIconResource() { return mBigIcon; } } /** * 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 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; // XXX Set FOCUS_AFTER_DESCENDANTS on embedded activities for now so // they can get // focus if none of their children have it. They need focus to be // able to // display menu items. // // Replace this with something better when Bug 628886 is fixed... // 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); } } } }