AppsCustomizeTabHost.java revision 21fadeaad1f5a662df425085551c6f54e8c28f52
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.launcher2; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.AnimatorSet; 22import android.animation.ObjectAnimator; 23import android.content.Context; 24import android.content.res.Resources; 25import android.util.AttributeSet; 26import android.view.LayoutInflater; 27import android.view.MotionEvent; 28import android.view.View; 29import android.view.ViewGroup; 30import android.widget.FrameLayout; 31import android.widget.LinearLayout; 32import android.widget.TabHost; 33import android.widget.TabWidget; 34import android.widget.TextView; 35 36import com.android.launcher.R; 37 38import java.util.ArrayList; 39 40public class AppsCustomizeTabHost extends TabHost implements LauncherTransitionable, 41 TabHost.OnTabChangeListener { 42 static final String LOG_TAG = "AppsCustomizeTabHost"; 43 44 private static final String APPS_TAB_TAG = "APPS"; 45 private static final String WIDGETS_TAB_TAG = "WIDGETS"; 46 47 private final LayoutInflater mLayoutInflater; 48 private ViewGroup mTabs; 49 private ViewGroup mTabsContainer; 50 private AppsCustomizePagedView mAppsCustomizePane; 51 private boolean mSuppressContentCallback = false; 52 private FrameLayout mAnimationBuffer; 53 private LinearLayout mContent; 54 55 private boolean mInTransition; 56 private boolean mTransitioningToWorkspace; 57 private boolean mResetAfterTransition; 58 private Runnable mRelayoutAndMakeVisible; 59 60 private Launcher mLauncher; 61 62 public AppsCustomizeTabHost(Context context, AttributeSet attrs) { 63 super(context, attrs); 64 mLayoutInflater = LayoutInflater.from(context); 65 mRelayoutAndMakeVisible = new Runnable() { 66 public void run() { 67 mTabs.requestLayout(); 68 mTabsContainer.setAlpha(1f); 69 } 70 }; 71 } 72 73 public void setup(Launcher launcher) { 74 mLauncher = launcher; 75 } 76 77 /** 78 * Convenience methods to select specific tabs. We want to set the content type immediately 79 * in these cases, but we note that we still call setCurrentTabByTag() so that the tab view 80 * reflects the new content (but doesn't do the animation and logic associated with changing 81 * tabs manually). 82 */ 83 private void setContentTypeImmediate(AppsCustomizePagedView.ContentType type) { 84 onTabChangedStart(); 85 onTabChangedEnd(type); 86 } 87 void selectAppsTab() { 88 setContentTypeImmediate(AppsCustomizePagedView.ContentType.Applications); 89 setCurrentTabByTag(APPS_TAB_TAG); 90 } 91 void selectWidgetsTab() { 92 setContentTypeImmediate(AppsCustomizePagedView.ContentType.Widgets); 93 setCurrentTabByTag(WIDGETS_TAB_TAG); 94 } 95 96 /** 97 * Setup the tab host and create all necessary tabs. 98 */ 99 @Override 100 protected void onFinishInflate() { 101 // Setup the tab host 102 setup(); 103 104 final ViewGroup tabsContainer = (ViewGroup) findViewById(R.id.tabs_container); 105 final TabWidget tabs = getTabWidget(); 106 final AppsCustomizePagedView appsCustomizePane = (AppsCustomizePagedView) 107 findViewById(R.id.apps_customize_pane_content); 108 mTabs = tabs; 109 mTabsContainer = tabsContainer; 110 mAppsCustomizePane = appsCustomizePane; 111 mAnimationBuffer = (FrameLayout) findViewById(R.id.animation_buffer); 112 mContent = (LinearLayout) findViewById(R.id.apps_customize_content); 113 if (tabs == null || mAppsCustomizePane == null) throw new Resources.NotFoundException(); 114 115 // Configure the tabs content factory to return the same paged view (that we change the 116 // content filter on) 117 TabContentFactory contentFactory = new TabContentFactory() { 118 public View createTabContent(String tag) { 119 return appsCustomizePane; 120 } 121 }; 122 123 // Create the tabs 124 TextView tabView; 125 String label; 126 label = getContext().getString(R.string.all_apps_button_label); 127 tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false); 128 tabView.setText(label); 129 tabView.setContentDescription(label); 130 addTab(newTabSpec(APPS_TAB_TAG).setIndicator(tabView).setContent(contentFactory)); 131 label = getContext().getString(R.string.widgets_tab_label); 132 tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false); 133 tabView.setText(label); 134 tabView.setContentDescription(label); 135 addTab(newTabSpec(WIDGETS_TAB_TAG).setIndicator(tabView).setContent(contentFactory)); 136 setOnTabChangedListener(this); 137 138 // Setup the key listener to jump between the last tab view and the market icon 139 AppsCustomizeTabKeyEventListener keyListener = new AppsCustomizeTabKeyEventListener(); 140 View lastTab = tabs.getChildTabViewAt(tabs.getTabCount() - 1); 141 lastTab.setOnKeyListener(keyListener); 142 View shopButton = findViewById(R.id.market_button); 143 shopButton.setOnKeyListener(keyListener); 144 145 // Hide the tab bar until we measure 146 mTabsContainer.setAlpha(0f); 147 } 148 149 @Override 150 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 151 boolean remeasureTabWidth = (mTabs.getLayoutParams().width <= 0); 152 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 153 154 // Set the width of the tab list to the content width 155 if (remeasureTabWidth) { 156 int contentWidth = mAppsCustomizePane.getPageContentWidth(); 157 if (contentWidth > 0 && mTabs.getLayoutParams().width != contentWidth) { 158 // Set the width and show the tab bar 159 mTabs.getLayoutParams().width = contentWidth; 160 post(mRelayoutAndMakeVisible); 161 } 162 } 163 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 164 } 165 166 public boolean onInterceptTouchEvent(MotionEvent ev) { 167 // If we are mid transitioning to the workspace, then intercept touch events here so we 168 // can ignore them, otherwise we just let all apps handle the touch events. 169 if (mInTransition && mTransitioningToWorkspace) { 170 return true; 171 } 172 return super.onInterceptTouchEvent(ev); 173 }; 174 175 @Override 176 public boolean onTouchEvent(MotionEvent event) { 177 // Allow touch events to fall through to the workspace if we are transitioning there 178 if (mInTransition && mTransitioningToWorkspace) { 179 return super.onTouchEvent(event); 180 } 181 182 // Intercept all touch events up to the bottom of the AppsCustomizePane so they do not fall 183 // through to the workspace and trigger showWorkspace() 184 if (event.getY() < mAppsCustomizePane.getBottom()) { 185 return true; 186 } 187 return super.onTouchEvent(event); 188 } 189 190 private void onTabChangedStart() { 191 mAppsCustomizePane.hideScrollingIndicator(false); 192 } 193 194 private void reloadCurrentPage() { 195 if (!LauncherApplication.isScreenLarge()) { 196 mAppsCustomizePane.flashScrollingIndicator(true); 197 } 198 mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); 199 mAppsCustomizePane.requestFocus(); 200 } 201 202 private void onTabChangedEnd(AppsCustomizePagedView.ContentType type) { 203 mAppsCustomizePane.setContentType(type); 204 } 205 206 @Override 207 public void onTabChanged(String tabId) { 208 final AppsCustomizePagedView.ContentType type = getContentTypeForTabTag(tabId); 209 if (mSuppressContentCallback) { 210 mSuppressContentCallback = false; 211 return; 212 } 213 214 // Animate the changing of the tab content by fading pages in and out 215 final Resources res = getResources(); 216 final int duration = res.getInteger(R.integer.config_tabTransitionDuration); 217 218 // We post a runnable here because there is a delay while the first page is loading and 219 // the feedback from having changed the tab almost feels better than having it stick 220 post(new Runnable() { 221 @Override 222 public void run() { 223 if (mAppsCustomizePane.getMeasuredWidth() <= 0 || 224 mAppsCustomizePane.getMeasuredHeight() <= 0) { 225 reloadCurrentPage(); 226 return; 227 } 228 229 // Take the visible pages and re-parent them temporarily to mAnimatorBuffer 230 // and then cross fade to the new pages 231 int[] visiblePageRange = new int[2]; 232 mAppsCustomizePane.getVisiblePages(visiblePageRange); 233 if (visiblePageRange[0] == -1 && visiblePageRange[1] == -1) { 234 // If we can't get the visible page ranges, then just skip the animation 235 reloadCurrentPage(); 236 return; 237 } 238 ArrayList<View> visiblePages = new ArrayList<View>(); 239 for (int i = visiblePageRange[0]; i <= visiblePageRange[1]; i++) { 240 visiblePages.add(mAppsCustomizePane.getPageAt(i)); 241 } 242 243 // We want the pages to be rendered in exactly the same way as they were when 244 // their parent was mAppsCustomizePane -- so set the scroll on mAnimationBuffer 245 // to be exactly the same as mAppsCustomizePane, and below, set the left/top 246 // parameters to be correct for each of the pages 247 mAnimationBuffer.scrollTo(mAppsCustomizePane.getScrollX(), 0); 248 249 // mAppsCustomizePane renders its children in reverse order, so 250 // add the pages to mAnimationBuffer in reverse order to match that behavior 251 for (int i = visiblePages.size() - 1; i >= 0; i--) { 252 View child = visiblePages.get(i); 253 if (child instanceof PagedViewCellLayout) { 254 ((PagedViewCellLayout) child).resetChildrenOnKeyListeners(); 255 } else if (child instanceof PagedViewGridLayout) { 256 ((PagedViewGridLayout) child).resetChildrenOnKeyListeners(); 257 } 258 PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(false); 259 mAppsCustomizePane.removeView(child); 260 PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(true); 261 mAnimationBuffer.setAlpha(1f); 262 mAnimationBuffer.setVisibility(View.VISIBLE); 263 LayoutParams p = new FrameLayout.LayoutParams(child.getMeasuredWidth(), 264 child.getMeasuredHeight()); 265 p.setMargins((int) child.getLeft(), (int) child.getTop(), 0, 0); 266 mAnimationBuffer.addView(child, p); 267 } 268 269 // Toggle the new content 270 onTabChangedStart(); 271 onTabChangedEnd(type); 272 273 // Animate the transition 274 ObjectAnimator outAnim = ObjectAnimator.ofFloat(mAnimationBuffer, "alpha", 0f); 275 outAnim.addListener(new AnimatorListenerAdapter() { 276 @Override 277 public void onAnimationEnd(Animator animation) { 278 mAnimationBuffer.setVisibility(View.GONE); 279 mAnimationBuffer.removeAllViews(); 280 } 281 @Override 282 public void onAnimationCancel(Animator animation) { 283 mAnimationBuffer.setVisibility(View.GONE); 284 mAnimationBuffer.removeAllViews(); 285 } 286 }); 287 ObjectAnimator inAnim = ObjectAnimator.ofFloat(mAppsCustomizePane, "alpha", 1f); 288 inAnim.addListener(new AnimatorListenerAdapter() { 289 @Override 290 public void onAnimationEnd(Animator animation) { 291 reloadCurrentPage(); 292 } 293 }); 294 AnimatorSet animSet = new AnimatorSet(); 295 animSet.playTogether(outAnim, inAnim); 296 animSet.setDuration(duration); 297 animSet.start(); 298 } 299 }); 300 } 301 302 public void setCurrentTabFromContent(AppsCustomizePagedView.ContentType type) { 303 mSuppressContentCallback = true; 304 setCurrentTabByTag(getTabTagForContentType(type)); 305 } 306 307 /** 308 * Returns the content type for the specified tab tag. 309 */ 310 public AppsCustomizePagedView.ContentType getContentTypeForTabTag(String tag) { 311 if (tag.equals(APPS_TAB_TAG)) { 312 return AppsCustomizePagedView.ContentType.Applications; 313 } else if (tag.equals(WIDGETS_TAB_TAG)) { 314 return AppsCustomizePagedView.ContentType.Widgets; 315 } 316 return AppsCustomizePagedView.ContentType.Applications; 317 } 318 319 /** 320 * Returns the tab tag for a given content type. 321 */ 322 public String getTabTagForContentType(AppsCustomizePagedView.ContentType type) { 323 if (type == AppsCustomizePagedView.ContentType.Applications) { 324 return APPS_TAB_TAG; 325 } else if (type == AppsCustomizePagedView.ContentType.Widgets) { 326 return WIDGETS_TAB_TAG; 327 } 328 return APPS_TAB_TAG; 329 } 330 331 /** 332 * Disable focus on anything under this view in the hierarchy if we are not visible. 333 */ 334 @Override 335 public int getDescendantFocusability() { 336 if (getVisibility() != View.VISIBLE) { 337 return ViewGroup.FOCUS_BLOCK_DESCENDANTS; 338 } 339 return super.getDescendantFocusability(); 340 } 341 342 void reset() { 343 if (mInTransition) { 344 // Defer to after the transition to reset 345 mResetAfterTransition = true; 346 } else { 347 // Reset immediately 348 mAppsCustomizePane.reset(); 349 } 350 } 351 352 private void enableAndBuildHardwareLayer() { 353 // isHardwareAccelerated() checks if we're attached to a window and if that 354 // window is HW accelerated-- we were sometimes not attached to a window 355 // and buildLayer was throwing an IllegalStateException 356 if (isHardwareAccelerated()) { 357 // Turn on hardware layers for performance 358 setLayerType(LAYER_TYPE_HARDWARE, null); 359 360 // force building the layer, so you don't get a blip early in an animation 361 // when the layer is created layer 362 buildLayer(); 363 364 // Let the GC system know that now is a good time to do any garbage 365 // collection; makes it less likely we'll get a GC during the all apps 366 // to workspace animation 367 System.gc(); 368 } 369 } 370 371 @Override 372 public View getContent() { 373 return mContent; 374 } 375 376 /* LauncherTransitionable overrides */ 377 @Override 378 public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) { 379 mAppsCustomizePane.onLauncherTransitionPrepare(l, animated, toWorkspace); 380 mInTransition = true; 381 mTransitioningToWorkspace = toWorkspace; 382 383 if (toWorkspace) { 384 // Going from All Apps -> Workspace 385 setVisibilityOfSiblingsWithLowerZOrder(VISIBLE); 386 // Stop the scrolling indicator - we don't want All Apps to be invalidating itself 387 // during the transition, especially since it has a hardware layer set on it 388 mAppsCustomizePane.cancelScrollingIndicatorAnimations(); 389 } else { 390 // Going from Workspace -> All Apps 391 mContent.setVisibility(VISIBLE); 392 393 // Make sure the current page is loaded (we start loading the side pages after the 394 // transition to prevent slowing down the animation) 395 mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true); 396 397 if (!LauncherApplication.isScreenLarge()) { 398 mAppsCustomizePane.showScrollingIndicator(true); 399 } 400 } 401 402 if (mResetAfterTransition) { 403 mAppsCustomizePane.reset(); 404 mResetAfterTransition = false; 405 } 406 } 407 408 @Override 409 public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) { 410 if (animated) { 411 enableAndBuildHardwareLayer(); 412 } 413 } 414 415 @Override 416 public void onLauncherTransitionStep(Launcher l, float t) { 417 // Do nothing 418 } 419 420 @Override 421 public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { 422 mAppsCustomizePane.onLauncherTransitionEnd(l, animated, toWorkspace); 423 mInTransition = false; 424 if (animated) { 425 setLayerType(LAYER_TYPE_NONE, null); 426 } 427 428 if (!toWorkspace) { 429 // Going from Workspace -> All Apps 430 setVisibilityOfSiblingsWithLowerZOrder(INVISIBLE); 431 432 // Dismiss the workspace cling and show the all apps cling (if not already shown) 433 l.dismissWorkspaceCling(null); 434 mAppsCustomizePane.showAllAppsCling(); 435 // Make sure adjacent pages are loaded (we wait until after the transition to 436 // prevent slowing down the animation) 437 mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); 438 439 if (!LauncherApplication.isScreenLarge()) { 440 mAppsCustomizePane.hideScrollingIndicator(false); 441 } 442 } 443 } 444 445 private void setVisibilityOfSiblingsWithLowerZOrder(int visibility) { 446 ViewGroup parent = (ViewGroup) getParent(); 447 if (parent == null) return; 448 449 final int count = parent.getChildCount(); 450 if (!isChildrenDrawingOrderEnabled()) { 451 for (int i = 0; i < count; i++) { 452 final View child = parent.getChildAt(i); 453 if (child == this) { 454 break; 455 } else { 456 if (child.getVisibility() == GONE) { 457 continue; 458 } 459 child.setVisibility(visibility); 460 } 461 } 462 } else { 463 throw new RuntimeException("Failed; can't get z-order of views"); 464 } 465 } 466 467 public void onWindowVisible() { 468 if (getVisibility() == VISIBLE) { 469 mContent.setVisibility(VISIBLE); 470 // We unload the widget previews when the UI is hidden, so need to reload pages 471 // Load the current page synchronously, and the neighboring pages asynchronously 472 mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true); 473 mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); 474 475 // We had to enable the wallpaper visibility when launching apps from all apps (so that 476 // the transitions would be the same as when launching from workspace) so we need to 477 // re-disable the wallpaper visibility to ensure performance. 478 int duration = getResources().getInteger(android.R.integer.config_shortAnimTime); 479 postDelayed(new Runnable() { 480 @Override 481 public void run() { 482 mLauncher.updateWallpaperVisibility(false); 483 } 484 }, duration); 485 } 486 } 487 488 public void onTrimMemory() { 489 mContent.setVisibility(GONE); 490 // Clear the widget pages of all their subviews - this will trigger the widget previews 491 // to delete their bitmaps 492 mAppsCustomizePane.clearAllWidgetPages(); 493 } 494 495 boolean isTransitioning() { 496 return mInTransition; 497 } 498} 499