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