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