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