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