| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.internal.widget; |
| |
| import com.android.internal.R; |
| |
| import android.util.TypedValue; |
| import android.view.ContextThemeWrapper; |
| import android.view.MotionEvent; |
| import android.widget.ActionMenuPresenter; |
| import android.widget.ActionMenuView; |
| |
| import android.animation.Animator; |
| import android.animation.AnimatorSet; |
| import android.animation.ObjectAnimator; |
| import android.animation.TimeInterpolator; |
| import android.content.Context; |
| import android.content.res.Configuration; |
| import android.content.res.TypedArray; |
| import android.util.AttributeSet; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.animation.DecelerateInterpolator; |
| |
| public abstract class AbsActionBarView extends ViewGroup { |
| private static final TimeInterpolator sAlphaInterpolator = new DecelerateInterpolator(); |
| |
| private static final int FADE_DURATION = 200; |
| |
| protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener(); |
| |
| /** Context against which to inflate popup menus. */ |
| protected final Context mPopupContext; |
| |
| protected ActionMenuView mMenuView; |
| protected ActionMenuPresenter mActionMenuPresenter; |
| protected ViewGroup mSplitView; |
| protected boolean mSplitActionBar; |
| protected boolean mSplitWhenNarrow; |
| protected int mContentHeight; |
| |
| protected Animator mVisibilityAnim; |
| |
| private boolean mEatingTouch; |
| private boolean mEatingHover; |
| |
| public AbsActionBarView(Context context) { |
| this(context, null); |
| } |
| |
| public AbsActionBarView(Context context, AttributeSet attrs) { |
| this(context, attrs, 0); |
| } |
| |
| public AbsActionBarView(Context context, AttributeSet attrs, int defStyleAttr) { |
| this(context, attrs, defStyleAttr, 0); |
| } |
| |
| public AbsActionBarView( |
| Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { |
| super(context, attrs, defStyleAttr, defStyleRes); |
| |
| final TypedValue tv = new TypedValue(); |
| if (context.getTheme().resolveAttribute(R.attr.actionBarPopupTheme, tv, true) |
| && tv.resourceId != 0) { |
| mPopupContext = new ContextThemeWrapper(context, tv.resourceId); |
| } else { |
| mPopupContext = context; |
| } |
| } |
| |
| @Override |
| protected void onConfigurationChanged(Configuration newConfig) { |
| super.onConfigurationChanged(newConfig); |
| |
| // Action bar can change size on configuration changes. |
| // Reread the desired height from the theme-specified style. |
| TypedArray a = getContext().obtainStyledAttributes(null, R.styleable.ActionBar, |
| com.android.internal.R.attr.actionBarStyle, 0); |
| setContentHeight(a.getLayoutDimension(R.styleable.ActionBar_height, 0)); |
| a.recycle(); |
| if (mSplitWhenNarrow) { |
| setSplitToolbar(getContext().getResources().getBoolean( |
| com.android.internal.R.bool.split_action_bar_is_narrow)); |
| } |
| if (mActionMenuPresenter != null) { |
| mActionMenuPresenter.onConfigurationChanged(newConfig); |
| } |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent ev) { |
| // ActionBarViews always eat touch events, but should still respect the touch event dispatch |
| // contract. If the normal View implementation doesn't want the events, we'll just silently |
| // eat the rest of the gesture without reporting the events to the default implementation |
| // since that's what it expects. |
| |
| final int action = ev.getActionMasked(); |
| if (action == MotionEvent.ACTION_DOWN) { |
| mEatingTouch = false; |
| } |
| |
| if (!mEatingTouch) { |
| final boolean handled = super.onTouchEvent(ev); |
| if (action == MotionEvent.ACTION_DOWN && !handled) { |
| mEatingTouch = true; |
| } |
| } |
| |
| if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { |
| mEatingTouch = false; |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public boolean onHoverEvent(MotionEvent ev) { |
| // Same deal as onTouchEvent() above. Eat all hover events, but still |
| // respect the touch event dispatch contract. |
| |
| final int action = ev.getActionMasked(); |
| if (action == MotionEvent.ACTION_HOVER_ENTER) { |
| mEatingHover = false; |
| } |
| |
| if (!mEatingHover) { |
| final boolean handled = super.onHoverEvent(ev); |
| if (action == MotionEvent.ACTION_HOVER_ENTER && !handled) { |
| mEatingHover = true; |
| } |
| } |
| |
| if (action == MotionEvent.ACTION_HOVER_EXIT |
| || action == MotionEvent.ACTION_CANCEL) { |
| mEatingHover = false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Sets whether the bar should be split right now, no questions asked. |
| * @param split true if the bar should split |
| */ |
| public void setSplitToolbar(boolean split) { |
| mSplitActionBar = split; |
| } |
| |
| /** |
| * Sets whether the bar should split if we enter a narrow screen configuration. |
| * @param splitWhenNarrow true if the bar should check to split after a config change |
| */ |
| public void setSplitWhenNarrow(boolean splitWhenNarrow) { |
| mSplitWhenNarrow = splitWhenNarrow; |
| } |
| |
| public void setContentHeight(int height) { |
| mContentHeight = height; |
| requestLayout(); |
| } |
| |
| public int getContentHeight() { |
| return mContentHeight; |
| } |
| |
| public void setSplitView(ViewGroup splitView) { |
| mSplitView = splitView; |
| } |
| |
| /** |
| * @return Current visibility or if animating, the visibility being animated to. |
| */ |
| public int getAnimatedVisibility() { |
| if (mVisibilityAnim != null) { |
| return mVisAnimListener.mFinalVisibility; |
| } |
| return getVisibility(); |
| } |
| |
| public Animator setupAnimatorToVisibility(int visibility, long duration) { |
| if (mVisibilityAnim != null) { |
| mVisibilityAnim.cancel(); |
| } |
| |
| if (visibility == VISIBLE) { |
| if (getVisibility() != VISIBLE) { |
| setAlpha(0); |
| if (mSplitView != null && mMenuView != null) { |
| mMenuView.setAlpha(0); |
| } |
| } |
| ObjectAnimator anim = ObjectAnimator.ofFloat(this, View.ALPHA, 1); |
| anim.setDuration(duration); |
| anim.setInterpolator(sAlphaInterpolator); |
| if (mSplitView != null && mMenuView != null) { |
| AnimatorSet set = new AnimatorSet(); |
| ObjectAnimator splitAnim = ObjectAnimator.ofFloat(mMenuView, View.ALPHA, 1); |
| splitAnim.setDuration(duration); |
| set.addListener(mVisAnimListener.withFinalVisibility(visibility)); |
| set.play(anim).with(splitAnim); |
| return set; |
| } else { |
| anim.addListener(mVisAnimListener.withFinalVisibility(visibility)); |
| return anim; |
| } |
| } else { |
| ObjectAnimator anim = ObjectAnimator.ofFloat(this, View.ALPHA, 0); |
| anim.setDuration(duration); |
| anim.setInterpolator(sAlphaInterpolator); |
| if (mSplitView != null && mMenuView != null) { |
| AnimatorSet set = new AnimatorSet(); |
| ObjectAnimator splitAnim = ObjectAnimator.ofFloat(mMenuView, View.ALPHA, 0); |
| splitAnim.setDuration(duration); |
| set.addListener(mVisAnimListener.withFinalVisibility(visibility)); |
| set.play(anim).with(splitAnim); |
| return set; |
| } else { |
| anim.addListener(mVisAnimListener.withFinalVisibility(visibility)); |
| return anim; |
| } |
| } |
| } |
| |
| public void animateToVisibility(int visibility) { |
| Animator anim = setupAnimatorToVisibility(visibility, FADE_DURATION); |
| anim.start(); |
| } |
| |
| @Override |
| public void setVisibility(int visibility) { |
| if (visibility != getVisibility()) { |
| if (mVisibilityAnim != null) { |
| mVisibilityAnim.end(); |
| } |
| super.setVisibility(visibility); |
| } |
| } |
| |
| public boolean showOverflowMenu() { |
| if (mActionMenuPresenter != null) { |
| return mActionMenuPresenter.showOverflowMenu(); |
| } |
| return false; |
| } |
| |
| public void postShowOverflowMenu() { |
| post(new Runnable() { |
| public void run() { |
| showOverflowMenu(); |
| } |
| }); |
| } |
| |
| public boolean hideOverflowMenu() { |
| if (mActionMenuPresenter != null) { |
| return mActionMenuPresenter.hideOverflowMenu(); |
| } |
| return false; |
| } |
| |
| public boolean isOverflowMenuShowing() { |
| if (mActionMenuPresenter != null) { |
| return mActionMenuPresenter.isOverflowMenuShowing(); |
| } |
| return false; |
| } |
| |
| public boolean isOverflowMenuShowPending() { |
| if (mActionMenuPresenter != null) { |
| return mActionMenuPresenter.isOverflowMenuShowPending(); |
| } |
| return false; |
| } |
| |
| public boolean isOverflowReserved() { |
| return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved(); |
| } |
| |
| public boolean canShowOverflowMenu() { |
| return isOverflowReserved() && getVisibility() == VISIBLE; |
| } |
| |
| public void dismissPopupMenus() { |
| if (mActionMenuPresenter != null) { |
| mActionMenuPresenter.dismissPopupMenus(); |
| } |
| } |
| |
| protected int measureChildView(View child, int availableWidth, int childSpecHeight, |
| int spacing) { |
| child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), |
| childSpecHeight); |
| |
| availableWidth -= child.getMeasuredWidth(); |
| availableWidth -= spacing; |
| |
| return Math.max(0, availableWidth); |
| } |
| |
| static protected int next(int x, int val, boolean isRtl) { |
| return isRtl ? x - val : x + val; |
| } |
| |
| protected int positionChild(View child, int x, int y, int contentHeight, boolean reverse) { |
| int childWidth = child.getMeasuredWidth(); |
| int childHeight = child.getMeasuredHeight(); |
| int childTop = y + (contentHeight - childHeight) / 2; |
| |
| if (reverse) { |
| child.layout(x - childWidth, childTop, x, childTop + childHeight); |
| } else { |
| child.layout(x, childTop, x + childWidth, childTop + childHeight); |
| } |
| |
| return (reverse ? -childWidth : childWidth); |
| } |
| |
| protected class VisibilityAnimListener implements Animator.AnimatorListener { |
| private boolean mCanceled = false; |
| int mFinalVisibility; |
| |
| public VisibilityAnimListener withFinalVisibility(int visibility) { |
| mFinalVisibility = visibility; |
| return this; |
| } |
| |
| @Override |
| public void onAnimationStart(Animator animation) { |
| setVisibility(VISIBLE); |
| mVisibilityAnim = animation; |
| mCanceled = false; |
| } |
| |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| if (mCanceled) return; |
| |
| mVisibilityAnim = null; |
| setVisibility(mFinalVisibility); |
| if (mSplitView != null && mMenuView != null) { |
| mMenuView.setVisibility(mFinalVisibility); |
| } |
| } |
| |
| @Override |
| public void onAnimationCancel(Animator animation) { |
| mCanceled = true; |
| } |
| |
| @Override |
| public void onAnimationRepeat(Animator animation) { |
| } |
| } |
| } |