From 325dc23624160689e59fbac708cf6f222b20d025 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Wed, 5 Jun 2013 22:57:57 -0400 Subject: Launcher2 is now Launcher3. Changes include - moving from com.android.launcher{,2} to com.android.launcher3 - removing wallpapers - new temporary icon Change-Id: I1eabd06059e94a8f3bdf6b620777bd1d2b7c212b --- .../android/launcher3/AppsCustomizeTabHost.java | 482 +++++++++++++++++++++ 1 file changed, 482 insertions(+) create mode 100644 src/com/android/launcher3/AppsCustomizeTabHost.java (limited to 'src/com/android/launcher3/AppsCustomizeTabHost.java') diff --git a/src/com/android/launcher3/AppsCustomizeTabHost.java b/src/com/android/launcher3/AppsCustomizeTabHost.java new file mode 100644 index 000000000..5d50fec03 --- /dev/null +++ b/src/com/android/launcher3/AppsCustomizeTabHost.java @@ -0,0 +1,482 @@ +/* + * 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.launcher3; + +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.launcher3.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 visiblePages = new ArrayList(); + 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; + } +} -- cgit v1.2.3