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 */ 16package com.android.internal.widget; 17 18import com.android.internal.R; 19 20import android.util.TypedValue; 21import android.view.ContextThemeWrapper; 22import android.view.MotionEvent; 23import android.widget.ActionMenuPresenter; 24import android.widget.ActionMenuView; 25 26import android.animation.Animator; 27import android.animation.AnimatorSet; 28import android.animation.ObjectAnimator; 29import android.animation.TimeInterpolator; 30import android.content.Context; 31import android.content.res.Configuration; 32import android.content.res.TypedArray; 33import android.util.AttributeSet; 34import android.view.View; 35import android.view.ViewGroup; 36import android.view.animation.DecelerateInterpolator; 37 38public abstract class AbsActionBarView extends ViewGroup { 39 private static final TimeInterpolator sAlphaInterpolator = new DecelerateInterpolator(); 40 41 private static final int FADE_DURATION = 200; 42 43 protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener(); 44 45 /** Context against which to inflate popup menus. */ 46 protected final Context mPopupContext; 47 48 protected ActionMenuView mMenuView; 49 protected ActionMenuPresenter mActionMenuPresenter; 50 protected ViewGroup mSplitView; 51 protected boolean mSplitActionBar; 52 protected boolean mSplitWhenNarrow; 53 protected int mContentHeight; 54 55 protected Animator mVisibilityAnim; 56 57 private boolean mEatingTouch; 58 private boolean mEatingHover; 59 60 public AbsActionBarView(Context context) { 61 this(context, null); 62 } 63 64 public AbsActionBarView(Context context, AttributeSet attrs) { 65 this(context, attrs, 0); 66 } 67 68 public AbsActionBarView(Context context, AttributeSet attrs, int defStyleAttr) { 69 this(context, attrs, defStyleAttr, 0); 70 } 71 72 public AbsActionBarView( 73 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 74 super(context, attrs, defStyleAttr, defStyleRes); 75 76 final TypedValue tv = new TypedValue(); 77 if (context.getTheme().resolveAttribute(R.attr.actionBarPopupTheme, tv, true) 78 && tv.resourceId != 0) { 79 mPopupContext = new ContextThemeWrapper(context, tv.resourceId); 80 } else { 81 mPopupContext = context; 82 } 83 } 84 85 @Override 86 protected void onConfigurationChanged(Configuration newConfig) { 87 super.onConfigurationChanged(newConfig); 88 89 // Action bar can change size on configuration changes. 90 // Reread the desired height from the theme-specified style. 91 TypedArray a = getContext().obtainStyledAttributes(null, R.styleable.ActionBar, 92 com.android.internal.R.attr.actionBarStyle, 0); 93 setContentHeight(a.getLayoutDimension(R.styleable.ActionBar_height, 0)); 94 a.recycle(); 95 if (mSplitWhenNarrow) { 96 setSplitToolbar(getContext().getResources().getBoolean( 97 com.android.internal.R.bool.split_action_bar_is_narrow)); 98 } 99 if (mActionMenuPresenter != null) { 100 mActionMenuPresenter.onConfigurationChanged(newConfig); 101 } 102 } 103 104 @Override 105 public boolean onTouchEvent(MotionEvent ev) { 106 // ActionBarViews always eat touch events, but should still respect the touch event dispatch 107 // contract. If the normal View implementation doesn't want the events, we'll just silently 108 // eat the rest of the gesture without reporting the events to the default implementation 109 // since that's what it expects. 110 111 final int action = ev.getActionMasked(); 112 if (action == MotionEvent.ACTION_DOWN) { 113 mEatingTouch = false; 114 } 115 116 if (!mEatingTouch) { 117 final boolean handled = super.onTouchEvent(ev); 118 if (action == MotionEvent.ACTION_DOWN && !handled) { 119 mEatingTouch = true; 120 } 121 } 122 123 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 124 mEatingTouch = false; 125 } 126 127 return true; 128 } 129 130 @Override 131 public boolean onHoverEvent(MotionEvent ev) { 132 // Same deal as onTouchEvent() above. Eat all hover events, but still 133 // respect the touch event dispatch contract. 134 135 final int action = ev.getActionMasked(); 136 if (action == MotionEvent.ACTION_HOVER_ENTER) { 137 mEatingHover = false; 138 } 139 140 if (!mEatingHover) { 141 final boolean handled = super.onHoverEvent(ev); 142 if (action == MotionEvent.ACTION_HOVER_ENTER && !handled) { 143 mEatingHover = true; 144 } 145 } 146 147 if (action == MotionEvent.ACTION_HOVER_EXIT 148 || action == MotionEvent.ACTION_CANCEL) { 149 mEatingHover = false; 150 } 151 152 return true; 153 } 154 155 /** 156 * Sets whether the bar should be split right now, no questions asked. 157 * @param split true if the bar should split 158 */ 159 public void setSplitToolbar(boolean split) { 160 mSplitActionBar = split; 161 } 162 163 /** 164 * Sets whether the bar should split if we enter a narrow screen configuration. 165 * @param splitWhenNarrow true if the bar should check to split after a config change 166 */ 167 public void setSplitWhenNarrow(boolean splitWhenNarrow) { 168 mSplitWhenNarrow = splitWhenNarrow; 169 } 170 171 public void setContentHeight(int height) { 172 mContentHeight = height; 173 requestLayout(); 174 } 175 176 public int getContentHeight() { 177 return mContentHeight; 178 } 179 180 public void setSplitView(ViewGroup splitView) { 181 mSplitView = splitView; 182 } 183 184 /** 185 * @return Current visibility or if animating, the visibility being animated to. 186 */ 187 public int getAnimatedVisibility() { 188 if (mVisibilityAnim != null) { 189 return mVisAnimListener.mFinalVisibility; 190 } 191 return getVisibility(); 192 } 193 194 public Animator setupAnimatorToVisibility(int visibility, long duration) { 195 if (mVisibilityAnim != null) { 196 mVisibilityAnim.cancel(); 197 } 198 199 if (visibility == VISIBLE) { 200 if (getVisibility() != VISIBLE) { 201 setAlpha(0); 202 if (mSplitView != null && mMenuView != null) { 203 mMenuView.setAlpha(0); 204 } 205 } 206 ObjectAnimator anim = ObjectAnimator.ofFloat(this, View.ALPHA, 1); 207 anim.setDuration(duration); 208 anim.setInterpolator(sAlphaInterpolator); 209 if (mSplitView != null && mMenuView != null) { 210 AnimatorSet set = new AnimatorSet(); 211 ObjectAnimator splitAnim = ObjectAnimator.ofFloat(mMenuView, View.ALPHA, 1); 212 splitAnim.setDuration(duration); 213 set.addListener(mVisAnimListener.withFinalVisibility(visibility)); 214 set.play(anim).with(splitAnim); 215 return set; 216 } else { 217 anim.addListener(mVisAnimListener.withFinalVisibility(visibility)); 218 return anim; 219 } 220 } else { 221 ObjectAnimator anim = ObjectAnimator.ofFloat(this, View.ALPHA, 0); 222 anim.setDuration(duration); 223 anim.setInterpolator(sAlphaInterpolator); 224 if (mSplitView != null && mMenuView != null) { 225 AnimatorSet set = new AnimatorSet(); 226 ObjectAnimator splitAnim = ObjectAnimator.ofFloat(mMenuView, View.ALPHA, 0); 227 splitAnim.setDuration(duration); 228 set.addListener(mVisAnimListener.withFinalVisibility(visibility)); 229 set.play(anim).with(splitAnim); 230 return set; 231 } else { 232 anim.addListener(mVisAnimListener.withFinalVisibility(visibility)); 233 return anim; 234 } 235 } 236 } 237 238 public void animateToVisibility(int visibility) { 239 Animator anim = setupAnimatorToVisibility(visibility, FADE_DURATION); 240 anim.start(); 241 } 242 243 @Override 244 public void setVisibility(int visibility) { 245 if (visibility != getVisibility()) { 246 if (mVisibilityAnim != null) { 247 mVisibilityAnim.end(); 248 } 249 super.setVisibility(visibility); 250 } 251 } 252 253 public boolean showOverflowMenu() { 254 if (mActionMenuPresenter != null) { 255 return mActionMenuPresenter.showOverflowMenu(); 256 } 257 return false; 258 } 259 260 public void postShowOverflowMenu() { 261 post(new Runnable() { 262 public void run() { 263 showOverflowMenu(); 264 } 265 }); 266 } 267 268 public boolean hideOverflowMenu() { 269 if (mActionMenuPresenter != null) { 270 return mActionMenuPresenter.hideOverflowMenu(); 271 } 272 return false; 273 } 274 275 public boolean isOverflowMenuShowing() { 276 if (mActionMenuPresenter != null) { 277 return mActionMenuPresenter.isOverflowMenuShowing(); 278 } 279 return false; 280 } 281 282 public boolean isOverflowMenuShowPending() { 283 if (mActionMenuPresenter != null) { 284 return mActionMenuPresenter.isOverflowMenuShowPending(); 285 } 286 return false; 287 } 288 289 public boolean isOverflowReserved() { 290 return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved(); 291 } 292 293 public boolean canShowOverflowMenu() { 294 return isOverflowReserved() && getVisibility() == VISIBLE; 295 } 296 297 public void dismissPopupMenus() { 298 if (mActionMenuPresenter != null) { 299 mActionMenuPresenter.dismissPopupMenus(); 300 } 301 } 302 303 protected int measureChildView(View child, int availableWidth, int childSpecHeight, 304 int spacing) { 305 child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), 306 childSpecHeight); 307 308 availableWidth -= child.getMeasuredWidth(); 309 availableWidth -= spacing; 310 311 return Math.max(0, availableWidth); 312 } 313 314 static protected int next(int x, int val, boolean isRtl) { 315 return isRtl ? x - val : x + val; 316 } 317 318 protected int positionChild(View child, int x, int y, int contentHeight, boolean reverse) { 319 int childWidth = child.getMeasuredWidth(); 320 int childHeight = child.getMeasuredHeight(); 321 int childTop = y + (contentHeight - childHeight) / 2; 322 323 if (reverse) { 324 child.layout(x - childWidth, childTop, x, childTop + childHeight); 325 } else { 326 child.layout(x, childTop, x + childWidth, childTop + childHeight); 327 } 328 329 return (reverse ? -childWidth : childWidth); 330 } 331 332 protected class VisibilityAnimListener implements Animator.AnimatorListener { 333 private boolean mCanceled = false; 334 int mFinalVisibility; 335 336 public VisibilityAnimListener withFinalVisibility(int visibility) { 337 mFinalVisibility = visibility; 338 return this; 339 } 340 341 @Override 342 public void onAnimationStart(Animator animation) { 343 setVisibility(VISIBLE); 344 mVisibilityAnim = animation; 345 mCanceled = false; 346 } 347 348 @Override 349 public void onAnimationEnd(Animator animation) { 350 if (mCanceled) return; 351 352 mVisibilityAnim = null; 353 setVisibility(mFinalVisibility); 354 if (mSplitView != null && mMenuView != null) { 355 mMenuView.setVisibility(mFinalVisibility); 356 } 357 } 358 359 @Override 360 public void onAnimationCancel(Animator animation) { 361 mCanceled = true; 362 } 363 364 @Override 365 public void onAnimationRepeat(Animator animation) { 366 } 367 } 368} 369