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 android.animation.Animator; 19 import android.animation.AnimatorSet; 20 import android.animation.ObjectAnimator; 21 import android.animation.TimeInterpolator; 22 import android.compat.annotation.UnsupportedAppUsage; 23 import android.content.Context; 24 import android.content.res.Configuration; 25 import android.content.res.TypedArray; 26 import android.util.AttributeSet; 27 import android.util.TypedValue; 28 import android.view.ContextThemeWrapper; 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.ActionMenuPresenter; 34 import android.widget.ActionMenuView; 35 36 import com.android.internal.R; 37 38 public 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 AbsActionBarView(Context context)60 public AbsActionBarView(Context context) { 61 this(context, null); 62 } 63 AbsActionBarView(Context context, AttributeSet attrs)64 public AbsActionBarView(Context context, AttributeSet attrs) { 65 this(context, attrs, 0); 66 } 67 AbsActionBarView(Context context, AttributeSet attrs, int defStyleAttr)68 public AbsActionBarView(Context context, AttributeSet attrs, int defStyleAttr) { 69 this(context, attrs, defStyleAttr, 0); 70 } 71 AbsActionBarView( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)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 onConfigurationChanged(Configuration newConfig)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 onTouchEvent(MotionEvent ev)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 onHoverEvent(MotionEvent ev)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 */ setSplitToolbar(boolean split)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 */ setSplitWhenNarrow(boolean splitWhenNarrow)167 public void setSplitWhenNarrow(boolean splitWhenNarrow) { 168 mSplitWhenNarrow = splitWhenNarrow; 169 } 170 setContentHeight(int height)171 public void setContentHeight(int height) { 172 mContentHeight = height; 173 requestLayout(); 174 } 175 getContentHeight()176 public int getContentHeight() { 177 return mContentHeight; 178 } 179 setSplitView(ViewGroup splitView)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 */ getAnimatedVisibility()187 public int getAnimatedVisibility() { 188 if (mVisibilityAnim != null) { 189 return mVisAnimListener.mFinalVisibility; 190 } 191 return getVisibility(); 192 } 193 setupAnimatorToVisibility(int visibility, long duration)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 animateToVisibility(int visibility)238 public void animateToVisibility(int visibility) { 239 Animator anim = setupAnimatorToVisibility(visibility, FADE_DURATION); 240 anim.start(); 241 } 242 243 @Override setVisibility(int visibility)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 showOverflowMenu()253 public boolean showOverflowMenu() { 254 if (mActionMenuPresenter != null) { 255 return mActionMenuPresenter.showOverflowMenu(); 256 } 257 return false; 258 } 259 postShowOverflowMenu()260 public void postShowOverflowMenu() { 261 post(new Runnable() { 262 public void run() { 263 showOverflowMenu(); 264 } 265 }); 266 } 267 hideOverflowMenu()268 public boolean hideOverflowMenu() { 269 if (mActionMenuPresenter != null) { 270 return mActionMenuPresenter.hideOverflowMenu(); 271 } 272 return false; 273 } 274 isOverflowMenuShowing()275 public boolean isOverflowMenuShowing() { 276 if (mActionMenuPresenter != null) { 277 return mActionMenuPresenter.isOverflowMenuShowing(); 278 } 279 return false; 280 } 281 isOverflowMenuShowPending()282 public boolean isOverflowMenuShowPending() { 283 if (mActionMenuPresenter != null) { 284 return mActionMenuPresenter.isOverflowMenuShowPending(); 285 } 286 return false; 287 } 288 isOverflowReserved()289 public boolean isOverflowReserved() { 290 return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved(); 291 } 292 canShowOverflowMenu()293 public boolean canShowOverflowMenu() { 294 return isOverflowReserved() && getVisibility() == VISIBLE; 295 } 296 297 @UnsupportedAppUsage dismissPopupMenus()298 public void dismissPopupMenus() { 299 if (mActionMenuPresenter != null) { 300 mActionMenuPresenter.dismissPopupMenus(); 301 } 302 } 303 measureChildView(View child, int availableWidth, int childSpecHeight, int spacing)304 protected int measureChildView(View child, int availableWidth, int childSpecHeight, 305 int spacing) { 306 child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), 307 childSpecHeight); 308 309 availableWidth -= child.getMeasuredWidth(); 310 availableWidth -= spacing; 311 312 return Math.max(0, availableWidth); 313 } 314 next(int x, int val, boolean isRtl)315 static protected int next(int x, int val, boolean isRtl) { 316 return isRtl ? x - val : x + val; 317 } 318 positionChild(View child, int x, int y, int contentHeight, boolean reverse)319 protected int positionChild(View child, int x, int y, int contentHeight, boolean reverse) { 320 int childWidth = child.getMeasuredWidth(); 321 int childHeight = child.getMeasuredHeight(); 322 int childTop = y + (contentHeight - childHeight) / 2; 323 324 if (reverse) { 325 child.layout(x - childWidth, childTop, x, childTop + childHeight); 326 } else { 327 child.layout(x, childTop, x + childWidth, childTop + childHeight); 328 } 329 330 return (reverse ? -childWidth : childWidth); 331 } 332 333 protected class VisibilityAnimListener implements Animator.AnimatorListener { 334 private boolean mCanceled = false; 335 int mFinalVisibility; 336 withFinalVisibility(int visibility)337 public VisibilityAnimListener withFinalVisibility(int visibility) { 338 mFinalVisibility = visibility; 339 return this; 340 } 341 342 @Override onAnimationStart(Animator animation)343 public void onAnimationStart(Animator animation) { 344 setVisibility(VISIBLE); 345 mVisibilityAnim = animation; 346 mCanceled = false; 347 } 348 349 @Override onAnimationEnd(Animator animation)350 public void onAnimationEnd(Animator animation) { 351 if (mCanceled) return; 352 353 mVisibilityAnim = null; 354 setVisibility(mFinalVisibility); 355 if (mSplitView != null && mMenuView != null) { 356 mMenuView.setVisibility(mFinalVisibility); 357 } 358 } 359 360 @Override onAnimationCancel(Animator animation)361 public void onAnimationCancel(Animator animation) { 362 mCanceled = true; 363 } 364 365 @Override onAnimationRepeat(Animator animation)366 public void onAnimationRepeat(Animator animation) { 367 } 368 } 369 } 370