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 17 package com.android.launcher2; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.AnimatorSet; 22 import android.animation.ObjectAnimator; 23 import android.content.Context; 24 import android.content.res.Resources; 25 import android.graphics.Bitmap; 26 import android.graphics.Canvas; 27 import android.util.AttributeSet; 28 import android.view.LayoutInflater; 29 import android.view.MotionEvent; 30 import android.view.View; 31 import android.view.ViewGroup; 32 import android.view.animation.DecelerateInterpolator; 33 import android.widget.ImageView; 34 import android.widget.TabHost; 35 import android.widget.TabWidget; 36 import android.widget.TextView; 37 38 import com.android.launcher.R; 39 40 public 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 AppsCustomizeTabHost(Context context, AttributeSet attrs)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 */ setContentTypeImmediate(AppsCustomizePagedView.ContentType type)68 private void setContentTypeImmediate(AppsCustomizePagedView.ContentType type) { 69 onTabChangedStart(); 70 onTabChangedEnd(type); 71 } selectAppsTab()72 void selectAppsTab() { 73 setContentTypeImmediate(AppsCustomizePagedView.ContentType.Applications); 74 setCurrentTabByTag(APPS_TAB_TAG); 75 } selectWidgetsTab()76 void selectWidgetsTab() { 77 setContentTypeImmediate(AppsCustomizePagedView.ContentType.Widgets); 78 setCurrentTabByTag(WIDGETS_TAB_TAG); 79 } 80 81 /** 82 * Setup the tab host and create all necessary tabs. 83 */ 84 @Override onFinishInflate()85 protected void onFinishInflate() { 86 // Setup the tab host 87 setup(); 88 89 final ViewGroup tabsContainer = (ViewGroup) findViewById(R.id.tabs_container); 90 final TabWidget tabs = (TabWidget) findViewById(com.android.internal.R.id.tabs); 91 final AppsCustomizePagedView appsCustomizePane = (AppsCustomizePagedView) 92 findViewById(R.id.apps_customize_pane_content); 93 mTabs = tabs; 94 mTabsContainer = tabsContainer; 95 mAppsCustomizePane = appsCustomizePane; 96 mAnimationBuffer = (ImageView) findViewById(R.id.animation_buffer); 97 if (tabs == null || mAppsCustomizePane == null) throw new Resources.NotFoundException(); 98 99 // Configure the tabs content factory to return the same paged view (that we change the 100 // content filter on) 101 TabContentFactory contentFactory = new TabContentFactory() { 102 public View createTabContent(String tag) { 103 return appsCustomizePane; 104 } 105 }; 106 107 // Create the tabs 108 TextView tabView; 109 String label; 110 label = mContext.getString(R.string.all_apps_button_label); 111 tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false); 112 tabView.setText(label); 113 tabView.setContentDescription(label); 114 addTab(newTabSpec(APPS_TAB_TAG).setIndicator(tabView).setContent(contentFactory)); 115 label = mContext.getString(R.string.widgets_tab_label); 116 tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false); 117 tabView.setText(label); 118 tabView.setContentDescription(label); 119 addTab(newTabSpec(WIDGETS_TAB_TAG).setIndicator(tabView).setContent(contentFactory)); 120 setOnTabChangedListener(this); 121 122 // Setup the key listener to jump between the last tab view and the market icon 123 AppsCustomizeTabKeyEventListener keyListener = new AppsCustomizeTabKeyEventListener(); 124 View lastTab = tabs.getChildTabViewAt(tabs.getTabCount() - 1); 125 lastTab.setOnKeyListener(keyListener); 126 View shopButton = findViewById(R.id.market_button); 127 shopButton.setOnKeyListener(keyListener); 128 129 // Hide the tab bar until we measure 130 mTabsContainer.setAlpha(0f); 131 } 132 133 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)134 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 135 boolean remeasureTabWidth = (mTabs.getLayoutParams().width <= 0); 136 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 137 138 // Set the width of the tab list to the content width 139 if (remeasureTabWidth) { 140 int contentWidth = mAppsCustomizePane.getPageContentWidth(); 141 if (contentWidth > 0) { 142 // Set the width and show the tab bar (if we have a loading graphic, we can switch 143 // it off here) 144 mTabs.getLayoutParams().width = contentWidth; 145 post(new Runnable() { 146 public void run() { 147 mTabs.requestLayout(); 148 mTabsContainer.setAlpha(1f); 149 } 150 }); 151 } 152 } 153 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 154 } 155 156 @Override onTouchEvent(MotionEvent event)157 public boolean onTouchEvent(MotionEvent event) { 158 // Intercept all touch events up to the bottom of the AppsCustomizePane so they do not fall 159 // through to the workspace and trigger showWorkspace() 160 if (event.getY() < mAppsCustomizePane.getBottom()) { 161 return true; 162 } 163 return super.onTouchEvent(event); 164 } 165 onTabChangedStart()166 private void onTabChangedStart() { 167 mAppsCustomizePane.hideScrollingIndicator(false); 168 } 169 reloadCurrentPage()170 private void reloadCurrentPage() { 171 if (!LauncherApplication.isScreenLarge()) { 172 mAppsCustomizePane.flashScrollingIndicator(); 173 } 174 mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); 175 mAppsCustomizePane.requestFocus(); 176 } 177 onTabChangedEnd(AppsCustomizePagedView.ContentType type)178 private void onTabChangedEnd(AppsCustomizePagedView.ContentType type) { 179 mAppsCustomizePane.setContentType(type); 180 } 181 182 @Override onTabChanged(String tabId)183 public void onTabChanged(String tabId) { 184 final AppsCustomizePagedView.ContentType type = getContentTypeForTabTag(tabId); 185 if (mSuppressContentCallback) { 186 mSuppressContentCallback = false; 187 return; 188 } 189 190 // Animate the changing of the tab content by fading pages in and out 191 final Resources res = getResources(); 192 final int duration = res.getInteger(R.integer.config_tabTransitionDuration); 193 194 // We post a runnable here because there is a delay while the first page is loading and 195 // the feedback from having changed the tab almost feels better than having it stick 196 post(new Runnable() { 197 @Override 198 public void run() { 199 if (mAppsCustomizePane.getMeasuredWidth() <= 0 || 200 mAppsCustomizePane.getMeasuredHeight() <= 0) { 201 reloadCurrentPage(); 202 return; 203 } 204 205 // Setup the animation buffer 206 Bitmap b = Bitmap.createBitmap(mAppsCustomizePane.getMeasuredWidth(), 207 mAppsCustomizePane.getMeasuredHeight(), Bitmap.Config.ARGB_8888); 208 Canvas c = new Canvas(b); 209 mAppsCustomizePane.draw(c); 210 mAppsCustomizePane.setAlpha(0f); 211 mAnimationBuffer.setImageBitmap(b); 212 mAnimationBuffer.setAlpha(1f); 213 mAnimationBuffer.setVisibility(View.VISIBLE); 214 c.setBitmap(null); 215 b = null; 216 217 // Toggle the new content 218 onTabChangedStart(); 219 onTabChangedEnd(type); 220 221 // Animate the transition 222 ObjectAnimator outAnim = ObjectAnimator.ofFloat(mAnimationBuffer, "alpha", 0f); 223 outAnim.addListener(new AnimatorListenerAdapter() { 224 @Override 225 public void onAnimationEnd(Animator animation) { 226 mAnimationBuffer.setVisibility(View.GONE); 227 mAnimationBuffer.setImageBitmap(null); 228 } 229 }); 230 ObjectAnimator inAnim = ObjectAnimator.ofFloat(mAppsCustomizePane, "alpha", 1f); 231 inAnim.addListener(new AnimatorListenerAdapter() { 232 @Override 233 public void onAnimationEnd(Animator animation) { 234 reloadCurrentPage(); 235 } 236 }); 237 AnimatorSet animSet = new AnimatorSet(); 238 animSet.playTogether(outAnim, inAnim); 239 animSet.setDuration(duration); 240 animSet.start(); 241 } 242 }); 243 } 244 setCurrentTabFromContent(AppsCustomizePagedView.ContentType type)245 public void setCurrentTabFromContent(AppsCustomizePagedView.ContentType type) { 246 mSuppressContentCallback = true; 247 setCurrentTabByTag(getTabTagForContentType(type)); 248 } 249 250 /** 251 * Returns the content type for the specified tab tag. 252 */ getContentTypeForTabTag(String tag)253 public AppsCustomizePagedView.ContentType getContentTypeForTabTag(String tag) { 254 if (tag.equals(APPS_TAB_TAG)) { 255 return AppsCustomizePagedView.ContentType.Applications; 256 } else if (tag.equals(WIDGETS_TAB_TAG)) { 257 return AppsCustomizePagedView.ContentType.Widgets; 258 } 259 return AppsCustomizePagedView.ContentType.Applications; 260 } 261 262 /** 263 * Returns the tab tag for a given content type. 264 */ getTabTagForContentType(AppsCustomizePagedView.ContentType type)265 public String getTabTagForContentType(AppsCustomizePagedView.ContentType type) { 266 if (type == AppsCustomizePagedView.ContentType.Applications) { 267 return APPS_TAB_TAG; 268 } else if (type == AppsCustomizePagedView.ContentType.Widgets) { 269 return WIDGETS_TAB_TAG; 270 } 271 return APPS_TAB_TAG; 272 } 273 274 /** 275 * Disable focus on anything under this view in the hierarchy if we are not visible. 276 */ 277 @Override getDescendantFocusability()278 public int getDescendantFocusability() { 279 if (getVisibility() != View.VISIBLE) { 280 return ViewGroup.FOCUS_BLOCK_DESCENDANTS; 281 } 282 return super.getDescendantFocusability(); 283 } 284 reset()285 void reset() { 286 if (mInTransition) { 287 // Defer to after the transition to reset 288 mResetAfterTransition = true; 289 } else { 290 // Reset immediately 291 mAppsCustomizePane.reset(); 292 } 293 } 294 295 /* LauncherTransitionable overrides */ 296 @Override onLauncherTransitionStart(Launcher l, Animator animation, boolean toWorkspace)297 public void onLauncherTransitionStart(Launcher l, Animator animation, boolean toWorkspace) { 298 mInTransition = true; 299 // isHardwareAccelerated() checks if we're attached to a window and if that 300 // window is HW accelerated-- we were sometimes not attached to a window 301 // and buildLayer was throwing an IllegalStateException 302 if (animation != null && isHardwareAccelerated()) { 303 // Turn on hardware layers for performance 304 setLayerType(LAYER_TYPE_HARDWARE, null); 305 306 // force building the layer at the beginning of the animation, so you don't get a 307 // blip early in the animation 308 buildLayer(); 309 } 310 if (!toWorkspace && !LauncherApplication.isScreenLarge()) { 311 mAppsCustomizePane.showScrollingIndicator(false); 312 } 313 if (mResetAfterTransition) { 314 mAppsCustomizePane.reset(); 315 mResetAfterTransition = false; 316 } 317 } 318 319 @Override onLauncherTransitionEnd(Launcher l, Animator animation, boolean toWorkspace)320 public void onLauncherTransitionEnd(Launcher l, Animator animation, boolean toWorkspace) { 321 mInTransition = false; 322 if (animation != null) { 323 setLayerType(LAYER_TYPE_NONE, null); 324 } 325 326 if (!toWorkspace) { 327 // Dismiss the cling if necessary 328 l.dismissWorkspaceCling(null); 329 330 if (!LauncherApplication.isScreenLarge()) { 331 mAppsCustomizePane.hideScrollingIndicator(false); 332 } 333 } 334 } 335 isTransitioning()336 boolean isTransitioning() { 337 return mInTransition; 338 } 339 } 340