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