/*
* Copyright (C) 2011 Google Inc.
*
* 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.google.tv.leftnavbar;
import android.app.ActionBar.Tab;
import android.app.Activity;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.res.TypedArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import java.util.HashMap;
import java.util.Map;
/**
* Handles the tab navigation mode.
*/
class TabDisplay {
/**
* Should be used to append a tab at the end of the list.
*
* @see #add(TabImpl, int, boolean)
*/
public static final int LAST_POSITION = -2;
private static final TabImpl NONE = null;
private final Context mContext;
private final TabAdapter mAdapter;
private TabListView mList;
private boolean mExpanded;
TabDisplay(Context context, ViewGroup parent, TypedArray attributes) {
mContext = context;
mAdapter = new TabAdapter(context);
createView(parent);
}
private void createView(ViewGroup parent) {
int resource = R.layout.leftnav_bar_tabs;
mList = (TabListView) LayoutInflater.from(mContext).inflate(resource, parent, false);
mList.setAdapter(mAdapter);
mList.setItemsCanFocus(true);
mList.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
}
View getView() {
return mList;
}
TabDisplay setVisible(boolean visible) {
mList.setVisibility(visible ? View.VISIBLE : View.GONE);
mAdapter.setSelectionActive(visible);
return this;
}
TabDisplay setExpanded(boolean expanded) {
mExpanded = expanded;
mAdapter.refresh();
return this;
}
/**
* @see #LAST_POSITION
*/
void add(TabImpl tab, int position, boolean setSelected) {
if (position == LAST_POSITION) {
position = mAdapter.getCount();
}
mAdapter.insert(tab, position);
if (setSelected) {
select(tab);
}
}
TabImpl get(int position) {
return mAdapter.getItem(position);
}
void select(TabImpl tab) {
mAdapter.setSelected(tab);
}
TabImpl getSelected() {
return mAdapter.getSelected();
}
private void onSelectionChanged(TabImpl oldSelection, TabImpl newSelection) {
FragmentTransaction transaction = null;
if (mContext instanceof Activity) {
transaction = ((Activity) mContext).getFragmentManager()
.beginTransaction()
.disallowAddToBackStack();
}
if (oldSelection == newSelection) {
if (newSelection != NONE && newSelection.getCallback() != null) {
newSelection.getCallback().onTabReselected(newSelection, transaction);
}
} else {
if (oldSelection != NONE && oldSelection.getCallback() != null) {
oldSelection.getCallback().onTabUnselected(oldSelection, transaction);
}
if (newSelection != NONE && newSelection.getCallback() != null) {
newSelection.getCallback().onTabSelected(newSelection, transaction);
}
}
if (transaction != null && !transaction.isEmpty()) {
transaction.commit();
}
mList.setHighlighted(mAdapter.getPosition(newSelection));
}
int getCount() {
return mAdapter.getCount();
}
void removeAll() {
mAdapter.clear();
}
void remove(TabImpl tab) {
mAdapter.remove(tab);
}
void remove(int position) {
remove(mAdapter.getItem(position));
}
/**
* Removes the given view from its parent if it has one.
*/
private static void detachFromParent(View view) {
if (view == null) {
return;
}
ViewGroup parent = (ViewGroup) view.getParent();
if (parent != null) {
parent.removeView(view);
}
}
/**
* Creates views for tabs, and handles the selection state.
*/
private final class TabAdapter extends ArrayAdapter<TabImpl> {
/**
* Holds the views created thus far.
*/
private final Map<TabImpl, TabFrame> mCachedViews;
/**
* The currently selected tab, or {@code TabDisplay#NONE} if none is selected.
*/
private TabImpl mSelection;
/**
* {@code true} if selection changes should trigger a callback and any visual change;
* otherwise the selection is just remembered.
*/
private boolean mIsSelectionActive;
/**
* When selection is not active, this simply remembers which tab was selected.
*/
private TabImpl mSavedSelection;
TabAdapter(Context context) {
super(context, 0);
mCachedViews = new HashMap<TabImpl, TabFrame>();
mSelection = NONE;
mSavedSelection = NONE;
mIsSelectionActive = true;
}
public void setSelectionActive(boolean active) {
if (active == mIsSelectionActive) {
return;
}
if (active) {
// Restore the saved selection.
mIsSelectionActive = true;
setSelected(mSavedSelection);
mSavedSelection = NONE;
} else {
// Save the selection and deselect the actual tab.
mSavedSelection = mSelection;
setSelected(NONE);
mIsSelectionActive = false;
}
}
public void setSelected(TabImpl tab) {
if (!mIsSelectionActive) {
// In this case, simply storing the selected tab.
mSavedSelection = tab;
return;
}
TabImpl oldSelection = mSelection;
mSelection = tab;
if (oldSelection != mSelection) {
setSelectionState(oldSelection, false);
setSelectionState(mSelection, true);
}
onSelectionChanged(oldSelection, mSelection);
}
public TabImpl getSelected() {
return mIsSelectionActive ? mSelection : mSavedSelection;
}
private boolean isSelected(TabImpl tab) {
return tab != NONE && tab == getSelected();
}
private void setSelectionState(TabImpl tab, boolean selected) {
if (tab != NONE && mCachedViews.containsKey(tab)) {
mCachedViews.get(tab).select(selected);
}
}
public void refresh() {
for (TabFrame frame : mCachedViews.values()) {
frame.expand(mExpanded);
}
}
@Override
public int getItemViewType(int position) {
// This ensures views are not recycled.
return IGNORE_ITEM_VIEW_TYPE;
}
@Override
public void insert(TabImpl tab, int position) {
super.insert(tab, position);
updatePositions(false /* normal order */);
}
@Override
public void remove(TabImpl tab) {
// Need to make sure custom views are properly removed from their parent.
detachFromParent(tab.getCustomView());
mCachedViews.remove(tab);
super.remove(tab);
updatePositions(false /* normal order */);
if (isSelected(tab)) {
setSelected(getCount() == 0 ? NONE : getItem(Math.max(0, tab.getPosition() - 1)));
}
tab.setPosition(Tab.INVALID_POSITION);
}
@Override
public void clear() {
updatePositions(true /* all invalid */);
for (int i = 0; i < getCount(); ++i) {
detachFromParent(getItem(i).getCustomView());
}
mCachedViews.clear();
setSelected(NONE);
super.clear();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final TabImpl tab = getItem(position);
if (!mCachedViews.containsKey(tab)) {
TabFrame frame = (TabFrame) LayoutInflater.from(getContext()).inflate(
R.layout.leftnav_bar_tab, parent, false);
if (tab.hasCustomView()) {
frame.configureCustom(tab.getCustomView());
} else {
frame.configureNormal(tab.getIcon(), tab.getText());
}
frame.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
setSelected(tab);
}
});
mCachedViews.put(tab, frame);
}
setSelectionState(tab, isSelected(tab));
TabFrame result = mCachedViews.get(tab);
result.expand(mExpanded);
return result;
}
/**
* Updates the tab objects so that they correctly report their position in the list.
*
* @param allInvalid {@code true} to set the position as "invalid" on all tabs
*/
private void updatePositions(boolean allInvalid) {
for (int i = 0; i < getCount(); ++i) {
getItem(i).setPosition(allInvalid ? Tab.INVALID_POSITION : i);
}
}
}
}