AppsCustomizeTabHost.java revision d2c1f80aa73bef9d7fb5991ec29ea7a7b2e38f90
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 ImageView 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 mAppsCustomizePane.setCurrentPageToWidgets(); 78 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 = (ImageView) findViewById(R.id.animation_buffer); 98 if (tabs == null || mAppsCustomizePane == null) throw new Resources.NotFoundException(); 99 100 // Configure the tabs content factory to return the same paged view (that we change the 101 // content filter on) 102 TabContentFactory contentFactory = new TabContentFactory() { 103 public View createTabContent(String tag) { 104 return appsCustomizePane; 105 } 106 }; 107 108 // Create the tabs 109 TextView tabView; 110 String label; 111 label = mContext.getString(R.string.all_apps_button_label); 112 tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false); 113 tabView.setText(label); 114 tabView.setContentDescription(label); 115 addTab(newTabSpec(APPS_TAB_TAG).setIndicator(tabView).setContent(contentFactory)); 116 label = mContext.getString(R.string.widgets_tab_label); 117 tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false); 118 tabView.setText(label); 119 tabView.setContentDescription(label); 120 addTab(newTabSpec(WIDGETS_TAB_TAG).setIndicator(tabView).setContent(contentFactory)); 121 setOnTabChangedListener(this); 122 123 // Setup the key listener to jump between the last tab view and the market icon 124 AppsCustomizeTabKeyEventListener keyListener = new AppsCustomizeTabKeyEventListener(); 125 View lastTab = tabs.getChildTabViewAt(tabs.getTabCount() - 1); 126 lastTab.setOnKeyListener(keyListener); 127 View shopButton = findViewById(R.id.market_button); 128 shopButton.setOnKeyListener(keyListener); 129 130 // Hide the tab bar until we measure 131 mTabsContainer.setAlpha(0f); 132 } 133 134 @Override 135 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 136 boolean remeasureTabWidth = (mTabs.getLayoutParams().width <= 0); 137 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 138 139 // Set the width of the tab list to the content width 140 if (remeasureTabWidth) { 141 int contentWidth = mAppsCustomizePane.getPageContentWidth(); 142 if (contentWidth > 0) { 143 // Set the width and show the tab bar (if we have a loading graphic, we can switch 144 // it off here) 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 private void onTabChangedEnd(AppsCustomizePagedView.ContentType type) { 171 mAppsCustomizePane.setContentType(type); 172 } 173 174 @Override 175 public void onTabChanged(String tabId) { 176 final AppsCustomizePagedView.ContentType type = getContentTypeForTabTag(tabId); 177 if (!mAppsCustomizePane.isContentType(type)) { 178 // Animate the changing of the tab content by fading pages in and out 179 final Resources res = getResources(); 180 final int duration = res.getInteger(R.integer.config_tabTransitionDuration); 181 182 // We post a runnable here because there is a delay while the first page is loading and 183 // the feedback from having changed the tab almost feels better than having it stick 184 post(new Runnable() { 185 @Override 186 public void run() { 187 // Setup the animation buffer 188 Bitmap b = Bitmap.createBitmap(mAppsCustomizePane.getMeasuredWidth(), 189 mAppsCustomizePane.getMeasuredHeight(), Bitmap.Config.ARGB_8888); 190 Canvas c = new Canvas(b); 191 mAppsCustomizePane.draw(c); 192 mAppsCustomizePane.setAlpha(0f); 193 mAnimationBuffer.setImageBitmap(b); 194 mAnimationBuffer.setAlpha(1f); 195 mAnimationBuffer.setVisibility(View.VISIBLE); 196 c.setBitmap(null); 197 b = null; 198 199 // Toggle the new content 200 onTabChangedStart(); 201 onTabChangedEnd(type); 202 203 // Animate the transition 204 ObjectAnimator outAnim = ObjectAnimator.ofFloat(mAnimationBuffer, "alpha", 0f); 205 outAnim.addListener(new AnimatorListenerAdapter() { 206 @Override 207 public void onAnimationEnd(Animator animation) { 208 mAnimationBuffer.setVisibility(View.GONE); 209 mAnimationBuffer.setImageBitmap(null); 210 } 211 }); 212 ObjectAnimator inAnim = ObjectAnimator.ofFloat(mAppsCustomizePane, "alpha", 1f); 213 inAnim.addListener(new AnimatorListenerAdapter() { 214 @Override 215 public void onAnimationEnd(Animator animation) { 216 if (!LauncherApplication.isScreenLarge()) { 217 mAppsCustomizePane.flashScrollingIndicator(); 218 } 219 mAppsCustomizePane.loadAssociatedPages( 220 mAppsCustomizePane.getCurrentPage()); 221 } 222 }); 223 AnimatorSet animSet = new AnimatorSet(); 224 animSet.playTogether(outAnim, inAnim); 225 animSet.setDuration(duration); 226 animSet.start(); 227 } 228 }); 229 } 230 } 231 232 /** 233 * Returns the content type for the specified tab tag. 234 */ 235 public AppsCustomizePagedView.ContentType getContentTypeForTabTag(String tag) { 236 if (tag.equals(APPS_TAB_TAG)) { 237 return AppsCustomizePagedView.ContentType.Applications; 238 } else if (tag.equals(WIDGETS_TAB_TAG)) { 239 return AppsCustomizePagedView.ContentType.Widgets; 240 } 241 return AppsCustomizePagedView.ContentType.Applications; 242 } 243 244 /** 245 * Returns the tab tag for a given content type. 246 */ 247 public String getTabTagForContentType(AppsCustomizePagedView.ContentType type) { 248 if (type == AppsCustomizePagedView.ContentType.Applications) { 249 return APPS_TAB_TAG; 250 } else if (type == AppsCustomizePagedView.ContentType.Widgets) { 251 return WIDGETS_TAB_TAG; 252 } 253 return APPS_TAB_TAG; 254 } 255 256 /** 257 * Disable focus on anything under this view in the hierarchy if we are not visible. 258 */ 259 @Override 260 public int getDescendantFocusability() { 261 if (getVisibility() != View.VISIBLE) { 262 return ViewGroup.FOCUS_BLOCK_DESCENDANTS; 263 } 264 return super.getDescendantFocusability(); 265 } 266 267 void reset() { 268 if (mInTransition) { 269 // Defer to after the transition to reset 270 mResetAfterTransition = true; 271 } else { 272 // Reset immediately 273 mAppsCustomizePane.reset(); 274 } 275 } 276 277 /* LauncherTransitionable overrides */ 278 @Override 279 public void onLauncherTransitionStart(Launcher l, Animator animation, boolean toWorkspace) { 280 mInTransition = true; 281 // isHardwareAccelerated() checks if we're attached to a window and if that 282 // window is HW accelerated-- we were sometimes not attached to a window 283 // and buildLayer was throwing an IllegalStateException 284 if (animation != null && isHardwareAccelerated()) { 285 // Turn on hardware layers for performance 286 setLayerType(LAYER_TYPE_HARDWARE, null); 287 288 // force building the layer at the beginning of the animation, so you don't get a 289 // blip early in the animation 290 buildLayer(); 291 } 292 if (!toWorkspace && !LauncherApplication.isScreenLarge()) { 293 mAppsCustomizePane.showScrollingIndicator(false); 294 } 295 if (mResetAfterTransition) { 296 mAppsCustomizePane.reset(); 297 mResetAfterTransition = false; 298 } 299 } 300 301 @Override 302 public void onLauncherTransitionEnd(Launcher l, Animator animation, boolean toWorkspace) { 303 mInTransition = false; 304 if (animation != null) { 305 setLayerType(LAYER_TYPE_NONE, null); 306 } 307 308 if (!toWorkspace) { 309 // Dismiss the cling if necessary 310 l.dismissWorkspaceCling(null); 311 312 if (!LauncherApplication.isScreenLarge()) { 313 mAppsCustomizePane.hideScrollingIndicator(false); 314 } 315 } 316 } 317 318 boolean isTransitioning() { 319 return mInTransition; 320 } 321} 322