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