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