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