AppsCustomizeTabHost.java revision 7fe4f8ba0a3c065773f3166940f6b34e42194389
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.graphics.Bitmap; 26import android.graphics.Canvas; 27import android.util.AttributeSet; 28import android.view.LayoutInflater; 29import android.view.MotionEvent; 30import android.view.View; 31import android.view.ViewGroup; 32import android.view.animation.DecelerateInterpolator; 33import android.widget.ImageView; 34import android.widget.TabHost; 35import android.widget.TabWidget; 36import android.widget.TextView; 37 38import com.android.launcher.R; 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 ImageView mAnimationBuffer; 53 54 private boolean mInTransition; 55 private boolean mResetAfterTransition; 56 57 public AppsCustomizeTabHost(Context context, AttributeSet attrs) { 58 super(context, attrs); 59 mLayoutInflater = LayoutInflater.from(context); 60 } 61 62 /** 63 * Convenience methods to select specific tabs. We want to set the content type immediately 64 * in these cases, but we note that we still call setCurrentTabByTag() so that the tab view 65 * reflects the new content (but doesn't do the animation and logic associated with changing 66 * tabs manually). 67 */ 68 private void setContentTypeImmediate(AppsCustomizePagedView.ContentType type) { 69 onTabChangedStart(); 70 onTabChangedEnd(type); 71 } 72 void selectAppsTab() { 73 setContentTypeImmediate(AppsCustomizePagedView.ContentType.Applications); 74 setCurrentTabByTag(APPS_TAB_TAG); 75 } 76 void selectWidgetsTab() { 77 setContentTypeImmediate(AppsCustomizePagedView.ContentType.Widgets); 78 mAppsCustomizePane.setCurrentPageToWidgets(); 79 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 = (ImageView) findViewById(R.id.animation_buffer); 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) { 144 // Set the width and show the tab bar (if we have a loading graphic, we can switch 145 // it off here) 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 @Override 159 public boolean onTouchEvent(MotionEvent event) { 160 // Intercept all touch events up to the bottom of the AppsCustomizePane so they do not fall 161 // through to the workspace and trigger showWorkspace() 162 if (event.getY() < mAppsCustomizePane.getBottom()) { 163 return true; 164 } 165 return super.onTouchEvent(event); 166 } 167 168 private void onTabChangedStart() { 169 mAppsCustomizePane.hideScrollingIndicator(false); 170 } 171 172 private void onTabChangedEnd(AppsCustomizePagedView.ContentType type) { 173 mAppsCustomizePane.setContentType(type); 174 } 175 176 @Override 177 public void onTabChanged(String tabId) { 178 final AppsCustomizePagedView.ContentType type = getContentTypeForTabTag(tabId); 179 if (mSuppressContentCallback) { 180 mSuppressContentCallback = false; 181 return; 182 } 183 184 // Animate the changing of the tab content by fading pages in and out 185 final Resources res = getResources(); 186 final int duration = res.getInteger(R.integer.config_tabTransitionDuration); 187 188 // We post a runnable here because there is a delay while the first page is loading and 189 // the feedback from having changed the tab almost feels better than having it stick 190 post(new Runnable() { 191 @Override 192 public void run() { 193 // Setup the animation buffer 194 Bitmap b = Bitmap.createBitmap(mAppsCustomizePane.getMeasuredWidth(), 195 mAppsCustomizePane.getMeasuredHeight(), Bitmap.Config.ARGB_8888); 196 Canvas c = new Canvas(b); 197 mAppsCustomizePane.draw(c); 198 mAppsCustomizePane.setAlpha(0f); 199 mAnimationBuffer.setImageBitmap(b); 200 mAnimationBuffer.setAlpha(1f); 201 mAnimationBuffer.setVisibility(View.VISIBLE); 202 c.setBitmap(null); 203 b = null; 204 205 // Toggle the new content 206 onTabChangedStart(); 207 onTabChangedEnd(type); 208 209 // Animate the transition 210 ObjectAnimator outAnim = ObjectAnimator.ofFloat(mAnimationBuffer, "alpha", 0f); 211 outAnim.addListener(new AnimatorListenerAdapter() { 212 @Override 213 public void onAnimationEnd(Animator animation) { 214 mAnimationBuffer.setVisibility(View.GONE); 215 mAnimationBuffer.setImageBitmap(null); 216 } 217 }); 218 ObjectAnimator inAnim = ObjectAnimator.ofFloat(mAppsCustomizePane, "alpha", 1f); 219 inAnim.addListener(new AnimatorListenerAdapter() { 220 @Override 221 public void onAnimationEnd(Animator animation) { 222 if (!LauncherApplication.isScreenLarge()) { 223 mAppsCustomizePane.flashScrollingIndicator(); 224 } 225 mAppsCustomizePane.loadAssociatedPages( 226 mAppsCustomizePane.getCurrentPage()); 227 } 228 }); 229 AnimatorSet animSet = new AnimatorSet(); 230 animSet.playTogether(outAnim, inAnim); 231 animSet.setDuration(duration); 232 animSet.start(); 233 } 234 }); 235 } 236 237 public void setCurrentTabFromContent(AppsCustomizePagedView.ContentType type) { 238 mSuppressContentCallback = true; 239 setCurrentTabByTag(getTabTagForContentType(type)); 240 } 241 242 /** 243 * Returns the content type for the specified tab tag. 244 */ 245 public AppsCustomizePagedView.ContentType getContentTypeForTabTag(String tag) { 246 if (tag.equals(APPS_TAB_TAG)) { 247 return AppsCustomizePagedView.ContentType.Applications; 248 } else if (tag.equals(WIDGETS_TAB_TAG)) { 249 return AppsCustomizePagedView.ContentType.Widgets; 250 } 251 return AppsCustomizePagedView.ContentType.Applications; 252 } 253 254 /** 255 * Returns the tab tag for a given content type. 256 */ 257 public String getTabTagForContentType(AppsCustomizePagedView.ContentType type) { 258 if (type == AppsCustomizePagedView.ContentType.Applications) { 259 return APPS_TAB_TAG; 260 } else if (type == AppsCustomizePagedView.ContentType.Widgets) { 261 return WIDGETS_TAB_TAG; 262 } 263 return APPS_TAB_TAG; 264 } 265 266 /** 267 * Disable focus on anything under this view in the hierarchy if we are not visible. 268 */ 269 @Override 270 public int getDescendantFocusability() { 271 if (getVisibility() != View.VISIBLE) { 272 return ViewGroup.FOCUS_BLOCK_DESCENDANTS; 273 } 274 return super.getDescendantFocusability(); 275 } 276 277 void reset() { 278 if (mInTransition) { 279 // Defer to after the transition to reset 280 mResetAfterTransition = true; 281 } else { 282 // Reset immediately 283 mAppsCustomizePane.reset(); 284 } 285 } 286 287 /* LauncherTransitionable overrides */ 288 @Override 289 public void onLauncherTransitionStart(Launcher l, Animator animation, boolean toWorkspace) { 290 mInTransition = true; 291 // isHardwareAccelerated() checks if we're attached to a window and if that 292 // window is HW accelerated-- we were sometimes not attached to a window 293 // and buildLayer was throwing an IllegalStateException 294 if (animation != null && isHardwareAccelerated()) { 295 // Turn on hardware layers for performance 296 setLayerType(LAYER_TYPE_HARDWARE, null); 297 298 // force building the layer at the beginning of the animation, so you don't get a 299 // blip early in the animation 300 buildLayer(); 301 } 302 if (!toWorkspace && !LauncherApplication.isScreenLarge()) { 303 mAppsCustomizePane.showScrollingIndicator(false); 304 } 305 if (mResetAfterTransition) { 306 mAppsCustomizePane.reset(); 307 mResetAfterTransition = false; 308 } 309 } 310 311 @Override 312 public void onLauncherTransitionEnd(Launcher l, Animator animation, boolean toWorkspace) { 313 mInTransition = false; 314 if (animation != null) { 315 setLayerType(LAYER_TYPE_NONE, null); 316 } 317 318 if (!toWorkspace) { 319 // Dismiss the cling if necessary 320 l.dismissWorkspaceCling(null); 321 322 if (!LauncherApplication.isScreenLarge()) { 323 mAppsCustomizePane.hideScrollingIndicator(false); 324 } 325 } 326 } 327 328 boolean isTransitioning() { 329 return mInTransition; 330 } 331} 332