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