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