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