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