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