| /* | |
| * Copyright (C) 2011 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.android.launcher2; | |
| import android.animation.Animator; | |
| import android.animation.AnimatorListenerAdapter; | |
| import android.animation.AnimatorSet; | |
| import android.animation.ObjectAnimator; | |
| import android.content.Context; | |
| import android.content.res.Resources; | |
| import android.util.AttributeSet; | |
| import android.view.LayoutInflater; | |
| import android.view.MotionEvent; | |
| import android.view.View; | |
| import android.view.ViewGroup; | |
| import android.widget.FrameLayout; | |
| import android.widget.LinearLayout; | |
| import android.widget.TabHost; | |
| import android.widget.TabWidget; | |
| import android.widget.TextView; | |
| import com.android.launcher.R; | |
| import java.util.ArrayList; | |
| public class AppsCustomizeTabHost extends TabHost implements LauncherTransitionable, | |
| TabHost.OnTabChangeListener { | |
| static final String LOG_TAG = "AppsCustomizeTabHost"; | |
| private static final String APPS_TAB_TAG = "APPS"; | |
| private static final String WIDGETS_TAB_TAG = "WIDGETS"; | |
| private final LayoutInflater mLayoutInflater; | |
| private ViewGroup mTabs; | |
| private ViewGroup mTabsContainer; | |
| private AppsCustomizePagedView mAppsCustomizePane; | |
| private FrameLayout mAnimationBuffer; | |
| private LinearLayout mContent; | |
| private boolean mInTransition; | |
| private boolean mTransitioningToWorkspace; | |
| private boolean mResetAfterTransition; | |
| private Runnable mRelayoutAndMakeVisible; | |
| public AppsCustomizeTabHost(Context context, AttributeSet attrs) { | |
| super(context, attrs); | |
| mLayoutInflater = LayoutInflater.from(context); | |
| mRelayoutAndMakeVisible = new Runnable() { | |
| public void run() { | |
| mTabs.requestLayout(); | |
| mTabsContainer.setAlpha(1f); | |
| } | |
| }; | |
| } | |
| /** | |
| * Convenience methods to select specific tabs. We want to set the content type immediately | |
| * in these cases, but we note that we still call setCurrentTabByTag() so that the tab view | |
| * reflects the new content (but doesn't do the animation and logic associated with changing | |
| * tabs manually). | |
| */ | |
| void setContentTypeImmediate(AppsCustomizePagedView.ContentType type) { | |
| setOnTabChangedListener(null); | |
| onTabChangedStart(); | |
| onTabChangedEnd(type); | |
| setCurrentTabByTag(getTabTagForContentType(type)); | |
| setOnTabChangedListener(this); | |
| } | |
| void selectAppsTab() { | |
| setContentTypeImmediate(AppsCustomizePagedView.ContentType.Applications); | |
| } | |
| void selectWidgetsTab() { | |
| setContentTypeImmediate(AppsCustomizePagedView.ContentType.Widgets); | |
| } | |
| /** | |
| * Setup the tab host and create all necessary tabs. | |
| */ | |
| @Override | |
| protected void onFinishInflate() { | |
| // Setup the tab host | |
| setup(); | |
| final ViewGroup tabsContainer = (ViewGroup) findViewById(R.id.tabs_container); | |
| final TabWidget tabs = getTabWidget(); | |
| final AppsCustomizePagedView appsCustomizePane = (AppsCustomizePagedView) | |
| findViewById(R.id.apps_customize_pane_content); | |
| mTabs = tabs; | |
| mTabsContainer = tabsContainer; | |
| mAppsCustomizePane = appsCustomizePane; | |
| mAnimationBuffer = (FrameLayout) findViewById(R.id.animation_buffer); | |
| mContent = (LinearLayout) findViewById(R.id.apps_customize_content); | |
| if (tabs == null || mAppsCustomizePane == null) throw new Resources.NotFoundException(); | |
| // Configure the tabs content factory to return the same paged view (that we change the | |
| // content filter on) | |
| TabContentFactory contentFactory = new TabContentFactory() { | |
| public View createTabContent(String tag) { | |
| return appsCustomizePane; | |
| } | |
| }; | |
| // Create the tabs | |
| TextView tabView; | |
| String label; | |
| label = getContext().getString(R.string.all_apps_button_label); | |
| tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false); | |
| tabView.setText(label); | |
| tabView.setContentDescription(label); | |
| addTab(newTabSpec(APPS_TAB_TAG).setIndicator(tabView).setContent(contentFactory)); | |
| label = getContext().getString(R.string.widgets_tab_label); | |
| tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false); | |
| tabView.setText(label); | |
| tabView.setContentDescription(label); | |
| addTab(newTabSpec(WIDGETS_TAB_TAG).setIndicator(tabView).setContent(contentFactory)); | |
| setOnTabChangedListener(this); | |
| // Setup the key listener to jump between the last tab view and the market icon | |
| AppsCustomizeTabKeyEventListener keyListener = new AppsCustomizeTabKeyEventListener(); | |
| View lastTab = tabs.getChildTabViewAt(tabs.getTabCount() - 1); | |
| lastTab.setOnKeyListener(keyListener); | |
| View shopButton = findViewById(R.id.market_button); | |
| shopButton.setOnKeyListener(keyListener); | |
| // Hide the tab bar until we measure | |
| mTabsContainer.setAlpha(0f); | |
| } | |
| @Override | |
| protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
| boolean remeasureTabWidth = (mTabs.getLayoutParams().width <= 0); | |
| super.onMeasure(widthMeasureSpec, heightMeasureSpec); | |
| // Set the width of the tab list to the content width | |
| if (remeasureTabWidth) { | |
| int contentWidth = mAppsCustomizePane.getPageContentWidth(); | |
| if (contentWidth > 0 && mTabs.getLayoutParams().width != contentWidth) { | |
| // Set the width and show the tab bar | |
| mTabs.getLayoutParams().width = contentWidth; | |
| mRelayoutAndMakeVisible.run(); | |
| } | |
| super.onMeasure(widthMeasureSpec, heightMeasureSpec); | |
| } | |
| } | |
| public boolean onInterceptTouchEvent(MotionEvent ev) { | |
| // If we are mid transitioning to the workspace, then intercept touch events here so we | |
| // can ignore them, otherwise we just let all apps handle the touch events. | |
| if (mInTransition && mTransitioningToWorkspace) { | |
| return true; | |
| } | |
| return super.onInterceptTouchEvent(ev); | |
| }; | |
| @Override | |
| public boolean onTouchEvent(MotionEvent event) { | |
| // Allow touch events to fall through to the workspace if we are transitioning there | |
| if (mInTransition && mTransitioningToWorkspace) { | |
| return super.onTouchEvent(event); | |
| } | |
| // Intercept all touch events up to the bottom of the AppsCustomizePane so they do not fall | |
| // through to the workspace and trigger showWorkspace() | |
| if (event.getY() < mAppsCustomizePane.getBottom()) { | |
| return true; | |
| } | |
| return super.onTouchEvent(event); | |
| } | |
| private void onTabChangedStart() { | |
| mAppsCustomizePane.hideScrollingIndicator(false); | |
| } | |
| private void reloadCurrentPage() { | |
| if (!LauncherApplication.isScreenLarge()) { | |
| mAppsCustomizePane.flashScrollingIndicator(true); | |
| } | |
| mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); | |
| mAppsCustomizePane.requestFocus(); | |
| } | |
| private void onTabChangedEnd(AppsCustomizePagedView.ContentType type) { | |
| mAppsCustomizePane.setContentType(type); | |
| } | |
| @Override | |
| public void onTabChanged(String tabId) { | |
| final AppsCustomizePagedView.ContentType type = getContentTypeForTabTag(tabId); | |
| // Animate the changing of the tab content by fading pages in and out | |
| final Resources res = getResources(); | |
| final int duration = res.getInteger(R.integer.config_tabTransitionDuration); | |
| // We post a runnable here because there is a delay while the first page is loading and | |
| // the feedback from having changed the tab almost feels better than having it stick | |
| post(new Runnable() { | |
| @Override | |
| public void run() { | |
| if (mAppsCustomizePane.getMeasuredWidth() <= 0 || | |
| mAppsCustomizePane.getMeasuredHeight() <= 0) { | |
| reloadCurrentPage(); | |
| return; | |
| } | |
| // Take the visible pages and re-parent them temporarily to mAnimatorBuffer | |
| // and then cross fade to the new pages | |
| int[] visiblePageRange = new int[2]; | |
| mAppsCustomizePane.getVisiblePages(visiblePageRange); | |
| if (visiblePageRange[0] == -1 && visiblePageRange[1] == -1) { | |
| // If we can't get the visible page ranges, then just skip the animation | |
| reloadCurrentPage(); | |
| return; | |
| } | |
| ArrayList<View> visiblePages = new ArrayList<View>(); | |
| for (int i = visiblePageRange[0]; i <= visiblePageRange[1]; i++) { | |
| visiblePages.add(mAppsCustomizePane.getPageAt(i)); | |
| } | |
| // We want the pages to be rendered in exactly the same way as they were when | |
| // their parent was mAppsCustomizePane -- so set the scroll on mAnimationBuffer | |
| // to be exactly the same as mAppsCustomizePane, and below, set the left/top | |
| // parameters to be correct for each of the pages | |
| mAnimationBuffer.scrollTo(mAppsCustomizePane.getScrollX(), 0); | |
| // mAppsCustomizePane renders its children in reverse order, so | |
| // add the pages to mAnimationBuffer in reverse order to match that behavior | |
| for (int i = visiblePages.size() - 1; i >= 0; i--) { | |
| View child = visiblePages.get(i); | |
| if (child instanceof PagedViewCellLayout) { | |
| ((PagedViewCellLayout) child).resetChildrenOnKeyListeners(); | |
| } else if (child instanceof PagedViewGridLayout) { | |
| ((PagedViewGridLayout) child).resetChildrenOnKeyListeners(); | |
| } | |
| PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(false); | |
| mAppsCustomizePane.removeView(child); | |
| PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(true); | |
| mAnimationBuffer.setAlpha(1f); | |
| mAnimationBuffer.setVisibility(View.VISIBLE); | |
| LayoutParams p = new FrameLayout.LayoutParams(child.getMeasuredWidth(), | |
| child.getMeasuredHeight()); | |
| p.setMargins((int) child.getLeft(), (int) child.getTop(), 0, 0); | |
| mAnimationBuffer.addView(child, p); | |
| } | |
| // Toggle the new content | |
| onTabChangedStart(); | |
| onTabChangedEnd(type); | |
| // Animate the transition | |
| ObjectAnimator outAnim = LauncherAnimUtils.ofFloat(mAnimationBuffer, "alpha", 0f); | |
| outAnim.addListener(new AnimatorListenerAdapter() { | |
| private void clearAnimationBuffer() { | |
| mAnimationBuffer.setVisibility(View.GONE); | |
| PagedViewWidget.setRecyclePreviewsWhenDetachedFromWindow(false); | |
| mAnimationBuffer.removeAllViews(); | |
| PagedViewWidget.setRecyclePreviewsWhenDetachedFromWindow(true); | |
| } | |
| @Override | |
| public void onAnimationEnd(Animator animation) { | |
| clearAnimationBuffer(); | |
| } | |
| @Override | |
| public void onAnimationCancel(Animator animation) { | |
| clearAnimationBuffer(); | |
| } | |
| }); | |
| ObjectAnimator inAnim = LauncherAnimUtils.ofFloat(mAppsCustomizePane, "alpha", 1f); | |
| inAnim.addListener(new AnimatorListenerAdapter() { | |
| @Override | |
| public void onAnimationEnd(Animator animation) { | |
| reloadCurrentPage(); | |
| } | |
| }); | |
| final AnimatorSet animSet = LauncherAnimUtils.createAnimatorSet(); | |
| animSet.playTogether(outAnim, inAnim); | |
| animSet.setDuration(duration); | |
| animSet.start(); | |
| } | |
| }); | |
| } | |
| public void setCurrentTabFromContent(AppsCustomizePagedView.ContentType type) { | |
| setOnTabChangedListener(null); | |
| setCurrentTabByTag(getTabTagForContentType(type)); | |
| setOnTabChangedListener(this); | |
| } | |
| /** | |
| * Returns the content type for the specified tab tag. | |
| */ | |
| public AppsCustomizePagedView.ContentType getContentTypeForTabTag(String tag) { | |
| if (tag.equals(APPS_TAB_TAG)) { | |
| return AppsCustomizePagedView.ContentType.Applications; | |
| } else if (tag.equals(WIDGETS_TAB_TAG)) { | |
| return AppsCustomizePagedView.ContentType.Widgets; | |
| } | |
| return AppsCustomizePagedView.ContentType.Applications; | |
| } | |
| /** | |
| * Returns the tab tag for a given content type. | |
| */ | |
| public String getTabTagForContentType(AppsCustomizePagedView.ContentType type) { | |
| if (type == AppsCustomizePagedView.ContentType.Applications) { | |
| return APPS_TAB_TAG; | |
| } else if (type == AppsCustomizePagedView.ContentType.Widgets) { | |
| return WIDGETS_TAB_TAG; | |
| } | |
| return APPS_TAB_TAG; | |
| } | |
| /** | |
| * Disable focus on anything under this view in the hierarchy if we are not visible. | |
| */ | |
| @Override | |
| public int getDescendantFocusability() { | |
| if (getVisibility() != View.VISIBLE) { | |
| return ViewGroup.FOCUS_BLOCK_DESCENDANTS; | |
| } | |
| return super.getDescendantFocusability(); | |
| } | |
| void reset() { | |
| if (mInTransition) { | |
| // Defer to after the transition to reset | |
| mResetAfterTransition = true; | |
| } else { | |
| // Reset immediately | |
| mAppsCustomizePane.reset(); | |
| } | |
| } | |
| private void enableAndBuildHardwareLayer() { | |
| // isHardwareAccelerated() checks if we're attached to a window and if that | |
| // window is HW accelerated-- we were sometimes not attached to a window | |
| // and buildLayer was throwing an IllegalStateException | |
| if (isHardwareAccelerated()) { | |
| // Turn on hardware layers for performance | |
| setLayerType(LAYER_TYPE_HARDWARE, null); | |
| // force building the layer, so you don't get a blip early in an animation | |
| // when the layer is created layer | |
| buildLayer(); | |
| } | |
| } | |
| @Override | |
| public View getContent() { | |
| return mContent; | |
| } | |
| /* LauncherTransitionable overrides */ | |
| @Override | |
| public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) { | |
| mAppsCustomizePane.onLauncherTransitionPrepare(l, animated, toWorkspace); | |
| mInTransition = true; | |
| mTransitioningToWorkspace = toWorkspace; | |
| if (toWorkspace) { | |
| // Going from All Apps -> Workspace | |
| setVisibilityOfSiblingsWithLowerZOrder(VISIBLE); | |
| // Stop the scrolling indicator - we don't want All Apps to be invalidating itself | |
| // during the transition, especially since it has a hardware layer set on it | |
| mAppsCustomizePane.cancelScrollingIndicatorAnimations(); | |
| } else { | |
| // Going from Workspace -> All Apps | |
| mContent.setVisibility(VISIBLE); | |
| // Make sure the current page is loaded (we start loading the side pages after the | |
| // transition to prevent slowing down the animation) | |
| mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true); | |
| if (!LauncherApplication.isScreenLarge()) { | |
| mAppsCustomizePane.showScrollingIndicator(true); | |
| } | |
| } | |
| if (mResetAfterTransition) { | |
| mAppsCustomizePane.reset(); | |
| mResetAfterTransition = false; | |
| } | |
| } | |
| @Override | |
| public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) { | |
| if (animated) { | |
| enableAndBuildHardwareLayer(); | |
| } | |
| } | |
| @Override | |
| public void onLauncherTransitionStep(Launcher l, float t) { | |
| // Do nothing | |
| } | |
| @Override | |
| public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { | |
| mAppsCustomizePane.onLauncherTransitionEnd(l, animated, toWorkspace); | |
| mInTransition = false; | |
| if (animated) { | |
| setLayerType(LAYER_TYPE_NONE, null); | |
| } | |
| if (!toWorkspace) { | |
| // Dismiss the workspace cling | |
| l.dismissWorkspaceCling(null); | |
| // Show the all apps cling (if not already shown) | |
| mAppsCustomizePane.showAllAppsCling(); | |
| // Make sure adjacent pages are loaded (we wait until after the transition to | |
| // prevent slowing down the animation) | |
| mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); | |
| if (!LauncherApplication.isScreenLarge()) { | |
| mAppsCustomizePane.hideScrollingIndicator(false); | |
| } | |
| // Going from Workspace -> All Apps | |
| // NOTE: We should do this at the end since we check visibility state in some of the | |
| // cling initialization/dismiss code above. | |
| setVisibilityOfSiblingsWithLowerZOrder(INVISIBLE); | |
| } | |
| } | |
| private void setVisibilityOfSiblingsWithLowerZOrder(int visibility) { | |
| ViewGroup parent = (ViewGroup) getParent(); | |
| if (parent == null) return; | |
| final int count = parent.getChildCount(); | |
| if (!isChildrenDrawingOrderEnabled()) { | |
| for (int i = 0; i < count; i++) { | |
| final View child = parent.getChildAt(i); | |
| if (child == this) { | |
| break; | |
| } else { | |
| if (child.getVisibility() == GONE) { | |
| continue; | |
| } | |
| child.setVisibility(visibility); | |
| } | |
| } | |
| } else { | |
| throw new RuntimeException("Failed; can't get z-order of views"); | |
| } | |
| } | |
| public void onWindowVisible() { | |
| if (getVisibility() == VISIBLE) { | |
| mContent.setVisibility(VISIBLE); | |
| // We unload the widget previews when the UI is hidden, so need to reload pages | |
| // Load the current page synchronously, and the neighboring pages asynchronously | |
| mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true); | |
| mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); | |
| } | |
| } | |
| public void onTrimMemory() { | |
| mContent.setVisibility(GONE); | |
| // Clear the widget pages of all their subviews - this will trigger the widget previews | |
| // to delete their bitmaps | |
| mAppsCustomizePane.clearAllWidgetPages(); | |
| } | |
| boolean isTransitioning() { | |
| return mInTransition; | |
| } | |
| } |