ActionBarOverlayLayout.java revision f3ad1351d8b40ec5defe35e79d3430ad3c384b6d
1/*
2 * Copyright (C) 2012 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.internal.widget;
18
19import android.content.Context;
20import android.content.res.Configuration;
21import android.content.res.TypedArray;
22import android.graphics.Canvas;
23import android.graphics.Rect;
24import android.graphics.drawable.Drawable;
25import android.os.Build;
26import android.os.Parcelable;
27import android.support.v4.view.ViewCompat;
28import android.support.v4.view.ViewPropertyAnimatorCompat;
29import android.support.v4.view.ViewPropertyAnimatorListener;
30import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
31import android.support.v4.widget.ScrollerCompat;
32import android.support.v7.appcompat.R;
33import android.support.v7.internal.VersionUtils;
34import android.support.v7.internal.view.menu.MenuPresenter;
35import android.support.v7.widget.Toolbar;
36import android.util.AttributeSet;
37import android.util.SparseArray;
38import android.view.Menu;
39import android.view.View;
40import android.view.ViewGroup;
41import android.view.Window;
42
43/**
44 * Special layout for the containing of an overlay action bar (and its content) to correctly handle
45 * fitting system windows when the content has request that its layout ignore them.
46 *
47 * @hide
48 */
49public class ActionBarOverlayLayout extends ViewGroup implements DecorContentParent {
50    private static final String TAG = "ActionBarOverlayLayout";
51
52    private int mActionBarHeight;
53    //private WindowDecorActionBar mActionBar;
54    private int mWindowVisibility = View.VISIBLE;
55
56    // The main UI elements that we handle the layout of.
57    private ContentFrameLayout mContent;
58    private ActionBarContainer mActionBarBottom;
59    private ActionBarContainer mActionBarTop;
60
61    // Some interior UI elements.
62    private DecorToolbar mDecorToolbar;
63
64    // Content overlay drawable - generally the action bar's shadow
65    private Drawable mWindowContentOverlay;
66    private boolean mIgnoreWindowContentOverlay;
67
68    private boolean mOverlayMode;
69    private boolean mHasNonEmbeddedTabs;
70    private boolean mHideOnContentScroll;
71    private boolean mAnimatingForFling;
72    private int mHideOnContentScrollReference;
73    private int mLastSystemUiVisibility;
74    private final Rect mBaseContentInsets = new Rect();
75    private final Rect mLastBaseContentInsets = new Rect();
76    private final Rect mContentInsets = new Rect();
77    private final Rect mBaseInnerInsets = new Rect();
78    private final Rect mInnerInsets = new Rect();
79    private final Rect mLastInnerInsets = new Rect();
80
81    private ActionBarVisibilityCallback mActionBarVisibilityCallback;
82
83    private final int ACTION_BAR_ANIMATE_DELAY = 600; // ms
84
85    private ScrollerCompat mFlingEstimator;
86
87    private ViewPropertyAnimatorCompat mCurrentActionBarTopAnimator;
88    private ViewPropertyAnimatorCompat mCurrentActionBarBottomAnimator;
89
90    private final ViewPropertyAnimatorListener mTopAnimatorListener
91            = new ViewPropertyAnimatorListenerAdapter() {
92        @Override
93        public void onAnimationEnd(View view) {
94            mCurrentActionBarTopAnimator = null;
95            mAnimatingForFling = false;
96        }
97
98        @Override
99        public void onAnimationCancel(View view) {
100            mCurrentActionBarTopAnimator = null;
101            mAnimatingForFling = false;
102        }
103    };
104
105    private final ViewPropertyAnimatorListener mBottomAnimatorListener =
106            new ViewPropertyAnimatorListenerAdapter() {
107        @Override
108        public void onAnimationEnd(View view) {
109            mCurrentActionBarBottomAnimator = null;
110            mAnimatingForFling = false;
111        }
112
113        @Override
114        public void onAnimationCancel(View view) {
115            mCurrentActionBarBottomAnimator = null;
116            mAnimatingForFling = false;
117        }
118    };
119
120    private final Runnable mRemoveActionBarHideOffset = new Runnable() {
121        public void run() {
122            haltActionBarHideOffsetAnimations();
123            mCurrentActionBarTopAnimator = ViewCompat.animate(mActionBarTop).translationY(0)
124                    .setListener(mTopAnimatorListener);
125            if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
126                mCurrentActionBarBottomAnimator = ViewCompat.animate(mActionBarBottom).translationY(0)
127                        .setListener(mBottomAnimatorListener);
128            }
129        }
130    };
131
132    private final Runnable mAddActionBarHideOffset = new Runnable() {
133        public void run() {
134            haltActionBarHideOffsetAnimations();
135            mCurrentActionBarTopAnimator = ViewCompat.animate(mActionBarTop)
136                    .translationY(-mActionBarTop.getHeight())
137                    .setListener(mTopAnimatorListener);
138            if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
139                mCurrentActionBarBottomAnimator =  ViewCompat.animate(mActionBarBottom)
140                        .translationY(mActionBarBottom.getHeight())
141                        .setListener(mBottomAnimatorListener);
142            }
143        }
144    };
145
146//    public static final Property<ActionBarOverlayLayout, Integer> ACTION_BAR_HIDE_OFFSET =
147//            new IntProperty<ActionBarOverlayLayout>("actionBarHideOffset") {
148//
149//                @Override
150//                public void setValue(ActionBarOverlayLayout object, int value) {
151//                    object.setActionBarHideOffset(value);
152//                }
153//
154//                @Override
155//                public Integer get(ActionBarOverlayLayout object) {
156//                    return object.getActionBarHideOffset();
157//                }
158//            };
159
160    static final int[] ATTRS = new int [] {
161            R.attr.actionBarSize,
162            android.R.attr.windowContentOverlay
163    };
164
165    public ActionBarOverlayLayout(Context context) {
166        super(context);
167        init(context);
168    }
169
170    public ActionBarOverlayLayout(Context context, AttributeSet attrs) {
171        super(context, attrs);
172        init(context);
173    }
174
175    private void init(Context context) {
176        TypedArray ta = getContext().getTheme().obtainStyledAttributes(ATTRS);
177        mActionBarHeight = ta.getDimensionPixelSize(0, 0);
178        mWindowContentOverlay = ta.getDrawable(1);
179        setWillNotDraw(mWindowContentOverlay == null);
180        ta.recycle();
181
182        mIgnoreWindowContentOverlay = context.getApplicationInfo().targetSdkVersion <
183                Build.VERSION_CODES.KITKAT;
184
185        mFlingEstimator = ScrollerCompat.create(context);
186    }
187
188    @Override
189    protected void onDetachedFromWindow() {
190        super.onDetachedFromWindow();
191        haltActionBarHideOffsetAnimations();
192    }
193
194    public void setActionBarVisibilityCallback(ActionBarVisibilityCallback cb) {
195        mActionBarVisibilityCallback = cb;
196        if (getWindowToken() != null) {
197            // This is being initialized after being added to a window;
198            // make sure to update all state now.
199            mActionBarVisibilityCallback.onWindowVisibilityChanged(mWindowVisibility);
200            if (mLastSystemUiVisibility != 0) {
201                int newVis = mLastSystemUiVisibility;
202                onWindowSystemUiVisibilityChanged(newVis);
203                ViewCompat.requestApplyInsets(this);
204            }
205        }
206    }
207
208    public void setOverlayMode(boolean overlayMode) {
209        mOverlayMode = overlayMode;
210
211        /*
212         * Drawing the window content overlay was broken before K so starting to draw it
213         * again unexpectedly will cause artifacts in some apps. They should fix it.
214         */
215        mIgnoreWindowContentOverlay = overlayMode &&
216                getContext().getApplicationInfo().targetSdkVersion <
217                        Build.VERSION_CODES.KITKAT;
218    }
219
220    public boolean isInOverlayMode() {
221        return mOverlayMode;
222    }
223
224    public void setHasNonEmbeddedTabs(boolean hasNonEmbeddedTabs) {
225        mHasNonEmbeddedTabs = hasNonEmbeddedTabs;
226    }
227
228    public void setShowingForActionMode(boolean showing) {
229        // TODO: Add workaround for this
230//        if (showing) {
231//            // Here's a fun hack: if the status bar is currently being hidden,
232//            // and the application has asked for stable content insets, then
233//            // we will end up with the action mode action bar being shown
234//            // without the status bar, but moved below where the status bar
235//            // would be.  Not nice.  Trying to have this be positioned
236//            // correctly is not easy (basically we need yet *another* content
237//            // inset from the window manager to know where to put it), so
238//            // instead we will just temporarily force the status bar to be shown.
239//            if ((getWindowSystemUiVisibility() & (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
240//                    | SYSTEM_UI_FLAG_LAYOUT_STABLE))
241//                    == (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE)) {
242//                setDisabledSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN);
243//            }
244//        } else {
245//            setDisabledSystemUiVisibility(0);
246//        }
247    }
248
249    protected void onConfigurationChanged(Configuration newConfig) {
250        if (Build.VERSION.SDK_INT >= 8) {
251            super.onConfigurationChanged(newConfig);
252        }
253        init(getContext());
254        ViewCompat.requestApplyInsets(this);
255    }
256
257    public void onWindowSystemUiVisibilityChanged(int visible) {
258        if (Build.VERSION.SDK_INT >= 16) {
259            super.onWindowSystemUiVisibilityChanged(visible);
260        }
261        pullChildren();
262        final int diff = mLastSystemUiVisibility ^ visible;
263        mLastSystemUiVisibility = visible;
264        final boolean barVisible = (visible & SYSTEM_UI_FLAG_FULLSCREEN) == 0;
265        final boolean stable = (visible & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
266        if (mActionBarVisibilityCallback != null) {
267            // We want the bar to be visible if it is not being hidden,
268            // or the app has not turned on a stable UI mode (meaning they
269            // are performing explicit layout around the action bar).
270            mActionBarVisibilityCallback.enableContentAnimations(!stable);
271            if (barVisible || !stable) mActionBarVisibilityCallback.showForSystem();
272            else mActionBarVisibilityCallback.hideForSystem();
273        }
274        if ((diff & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
275            if (mActionBarVisibilityCallback != null) {
276                ViewCompat.requestApplyInsets(this);
277            }
278        }
279    }
280
281    @Override
282    protected void onWindowVisibilityChanged(int visibility) {
283        super.onWindowVisibilityChanged(visibility);
284        mWindowVisibility = visibility;
285        if (mActionBarVisibilityCallback != null) {
286            mActionBarVisibilityCallback.onWindowVisibilityChanged(visibility);
287        }
288    }
289
290    private boolean applyInsets(View view, Rect insets, boolean left, boolean top,
291            boolean bottom, boolean right) {
292        boolean changed = false;
293        LayoutParams lp = (LayoutParams)view.getLayoutParams();
294        if (left && lp.leftMargin != insets.left) {
295            changed = true;
296            lp.leftMargin = insets.left;
297        }
298        if (top && lp.topMargin != insets.top) {
299            changed = true;
300            lp.topMargin = insets.top;
301        }
302        if (right && lp.rightMargin != insets.right) {
303            changed = true;
304            lp.rightMargin = insets.right;
305        }
306        if (bottom && lp.bottomMargin != insets.bottom) {
307            changed = true;
308            lp.bottomMargin = insets.bottom;
309        }
310        return changed;
311    }
312
313    @Override
314    protected boolean fitSystemWindows(Rect insets) {
315        pullChildren();
316
317        final int vis = ViewCompat.getWindowSystemUiVisibility(this);
318        final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
319        final Rect systemInsets = insets;
320
321        // Since we're not the top level view in the window decor, we do not need to
322        // inset the Action Bars
323
324        boolean changed = false;
325        mBaseInnerInsets.set(systemInsets);
326        ViewUtils.computeFitSystemWindows(this, mBaseInnerInsets, mBaseContentInsets);
327        if (!mLastBaseContentInsets.equals(mBaseContentInsets)) {
328            changed = true;
329            mLastBaseContentInsets.set(mBaseContentInsets);
330        }
331
332        if (changed) {
333            requestLayout();
334        }
335
336        // We don't do any more at this point.  To correctly compute the content/inner
337        // insets in all cases, we need to know the measured size of the various action
338        // bar elements. fitSystemWindows() happens before the measure pass, so we can't
339        // do that here. Instead we will take this up in onMeasure().
340        return true;
341    }
342
343    @Override
344    protected LayoutParams generateDefaultLayoutParams() {
345        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
346    }
347
348    @Override
349    public LayoutParams generateLayoutParams(AttributeSet attrs) {
350        return new LayoutParams(getContext(), attrs);
351    }
352
353    @Override
354    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
355        return new LayoutParams(p);
356    }
357
358    @Override
359    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
360        return p instanceof LayoutParams;
361    }
362
363    @Override
364    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
365        pullChildren();
366
367        int maxHeight = 0;
368        int maxWidth = 0;
369        int childState = 0;
370
371        int topInset = 0;
372        int bottomInset = 0;
373
374        measureChildWithMargins(mActionBarTop, widthMeasureSpec, 0, heightMeasureSpec, 0);
375        LayoutParams lp = (LayoutParams) mActionBarTop.getLayoutParams();
376        maxWidth = Math.max(maxWidth,
377                mActionBarTop.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
378        maxHeight = Math.max(maxHeight,
379                mActionBarTop.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
380        childState = ViewUtils.combineMeasuredStates(childState,
381                ViewCompat.getMeasuredState(mActionBarTop));
382
383        // xlarge screen layout doesn't have bottom action bar.
384        if (mActionBarBottom != null) {
385            measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0);
386            lp = (LayoutParams) mActionBarBottom.getLayoutParams();
387            maxWidth = Math.max(maxWidth,
388                    mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
389            maxHeight = Math.max(maxHeight,
390                    mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
391            childState = ViewUtils.combineMeasuredStates(childState,
392                    ViewCompat.getMeasuredState(mActionBarBottom));
393        }
394
395        final int vis = ViewCompat.getWindowSystemUiVisibility(this);
396        final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
397
398        if (stable) {
399            // This is the standard space needed for the action bar.  For stable measurement,
400            // we can't depend on the size currently reported by it -- this must remain constant.
401            topInset = mActionBarHeight;
402            if (mHasNonEmbeddedTabs) {
403                final View tabs = mActionBarTop.getTabContainer();
404                if (tabs != null) {
405                    // If tabs are not embedded, increase space on top to account for them.
406                    topInset += mActionBarHeight;
407                }
408            }
409        } else if (mActionBarTop.getVisibility() != GONE) {
410            // This is the space needed on top of the window for all of the action bar
411            // and tabs.
412            topInset = mActionBarTop.getMeasuredHeight();
413        }
414
415        if (mDecorToolbar.isSplit()) {
416            // If action bar is split, adjust bottom insets for it.
417            if (mActionBarBottom != null) {
418                if (stable) {
419                    bottomInset = mActionBarHeight;
420                } else {
421                    bottomInset = mActionBarBottom.getMeasuredHeight();
422                }
423            }
424        }
425
426        // If the window has not requested system UI layout flags, we need to
427        // make sure its content is not being covered by system UI...  though it
428        // will still be covered by the action bar if they have requested it to
429        // overlay.
430        mContentInsets.set(mBaseContentInsets);
431        mInnerInsets.set(mBaseInnerInsets);
432        if (!mOverlayMode && !stable) {
433            mContentInsets.top += topInset;
434            mContentInsets.bottom += bottomInset;
435        } else {
436            mInnerInsets.top += topInset;
437            mInnerInsets.bottom += bottomInset;
438        }
439        applyInsets(mContent, mContentInsets, true, true, true, true);
440
441        if (!mLastInnerInsets.equals(mInnerInsets)) {
442            // If the inner insets have changed, we need to dispatch this down to
443            // the app's fitSystemWindows().  We do this before measuring the content
444            // view to keep the same semantics as the normal fitSystemWindows() call.
445            mLastInnerInsets.set(mInnerInsets);
446
447            mContent.dispatchFitSystemWindows(mInnerInsets);
448        }
449
450        measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
451        lp = (LayoutParams) mContent.getLayoutParams();
452        maxWidth = Math.max(maxWidth,
453                mContent.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
454        maxHeight = Math.max(maxHeight,
455                mContent.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
456        childState = ViewUtils.combineMeasuredStates(childState,
457                ViewCompat.getMeasuredState(mContent));
458
459        // Account for padding too
460        maxWidth += getPaddingLeft() + getPaddingRight();
461        maxHeight += getPaddingTop() + getPaddingBottom();
462
463        // Check against our minimum height and width
464        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
465        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
466
467        setMeasuredDimension(
468                ViewCompat.resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
469                ViewCompat.resolveSizeAndState(maxHeight, heightMeasureSpec,
470                        childState << MEASURED_HEIGHT_STATE_SHIFT));
471    }
472
473    @Override
474    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
475        final int count = getChildCount();
476
477        final int parentLeft = getPaddingLeft();
478        final int parentRight = right - left - getPaddingRight();
479
480        final int parentTop = getPaddingTop();
481        final int parentBottom = bottom - top - getPaddingBottom();
482
483        for (int i = 0; i < count; i++) {
484            final View child = getChildAt(i);
485            if (child.getVisibility() != GONE) {
486                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
487
488                final int width = child.getMeasuredWidth();
489                final int height = child.getMeasuredHeight();
490
491                int childLeft = parentLeft + lp.leftMargin;
492                int childTop;
493                if (child == mActionBarBottom) {
494                    childTop = parentBottom - height - lp.bottomMargin;
495                } else {
496                    childTop = parentTop + lp.topMargin;
497                }
498
499                child.layout(childLeft, childTop, childLeft + width, childTop + height);
500            }
501        }
502    }
503
504    @Override
505    public void draw(Canvas c) {
506        super.draw(c);
507        if (mWindowContentOverlay != null && !mIgnoreWindowContentOverlay) {
508            final int top = mActionBarTop.getVisibility() == VISIBLE ?
509                    (int) (mActionBarTop.getBottom() + ViewCompat.getTranslationY(mActionBarTop) + 0.5f)
510                    : 0;
511            mWindowContentOverlay.setBounds(0, top, getWidth(),
512                    top + mWindowContentOverlay.getIntrinsicHeight());
513            mWindowContentOverlay.draw(c);
514        }
515    }
516
517    @Override
518    public boolean shouldDelayChildPressedState() {
519        return false;
520    }
521
522    @Override
523    public boolean onStartNestedScroll(View child, View target, int axes) {
524        if ((axes & SCROLL_AXIS_VERTICAL) == 0 || mActionBarTop.getVisibility() != VISIBLE) {
525            return false;
526        }
527        return mHideOnContentScroll;
528    }
529
530    @Override
531    public void onNestedScrollAccepted(View child, View target, int axes) {
532        super.onNestedScrollAccepted(child, target, axes);
533        mHideOnContentScrollReference = getActionBarHideOffset();
534        haltActionBarHideOffsetAnimations();
535        if (mActionBarVisibilityCallback != null) {
536            mActionBarVisibilityCallback.onContentScrollStarted();
537        }
538    }
539
540    @Override
541    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
542            int dxUnconsumed, int dyUnconsumed) {
543        mHideOnContentScrollReference += dyConsumed;
544        setActionBarHideOffset(mHideOnContentScrollReference);
545    }
546
547    @Override
548    public void onStopNestedScroll(View target) {
549        super.onStopNestedScroll(target);
550        if (mHideOnContentScroll && !mAnimatingForFling) {
551            if (mHideOnContentScrollReference <= mActionBarTop.getHeight()) {
552                postRemoveActionBarHideOffset();
553            } else {
554                postAddActionBarHideOffset();
555            }
556        }
557        if (mActionBarVisibilityCallback != null) {
558            mActionBarVisibilityCallback.onContentScrollStopped();
559        }
560    }
561
562    @Override
563    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
564        if (!mHideOnContentScroll || !consumed) {
565            return false;
566        }
567        if (shouldHideActionBarOnFling(velocityX, velocityY)) {
568            addActionBarHideOffset();
569        } else {
570            removeActionBarHideOffset();
571        }
572        mAnimatingForFling = true;
573        return true;
574    }
575
576    void pullChildren() {
577        if (mContent == null) {
578            mContent = (ContentFrameLayout) findViewById(R.id.action_bar_activity_content);
579            mActionBarTop = (ActionBarContainer) findViewById(R.id.action_bar_container);
580            mDecorToolbar = getDecorToolbar(findViewById(R.id.action_bar));
581            mActionBarBottom = (ActionBarContainer) findViewById(R.id.split_action_bar);
582        }
583    }
584
585    private DecorToolbar getDecorToolbar(View view) {
586        if (view instanceof DecorToolbar) {
587            return (DecorToolbar) view;
588        } else if (view instanceof Toolbar) {
589            return ((Toolbar) view).getWrapper();
590        } else {
591            throw new IllegalStateException("Can't make a decor toolbar out of " +
592                    view.getClass().getSimpleName());
593        }
594    }
595
596    public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
597        if (hideOnContentScroll != mHideOnContentScroll) {
598            mHideOnContentScroll = hideOnContentScroll;
599            if (!hideOnContentScroll) {
600                if (VersionUtils.isAtLeastL()) {
601                    stopNestedScroll();
602                }
603                haltActionBarHideOffsetAnimations();
604                setActionBarHideOffset(0);
605            }
606        }
607    }
608
609    public boolean isHideOnContentScrollEnabled() {
610        return mHideOnContentScroll;
611    }
612
613    public int getActionBarHideOffset() {
614        return mActionBarTop != null ? -((int) ViewCompat.getTranslationY(mActionBarTop)) : 0;
615    }
616
617    public void setActionBarHideOffset(int offset) {
618        haltActionBarHideOffsetAnimations();
619        final int topHeight = mActionBarTop.getHeight();
620        offset = Math.max(0, Math.min(offset, topHeight));
621        ViewCompat.setTranslationY(mActionBarTop, -offset);
622        if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
623            // Match the hide offset proportionally for a split bar
624            final float fOffset = (float) offset / topHeight;
625            final int bOffset = (int) (mActionBarBottom.getHeight() * fOffset);
626            ViewCompat.setTranslationY(mActionBarBottom, bOffset);
627        }
628    }
629
630    private void haltActionBarHideOffsetAnimations() {
631        removeCallbacks(mRemoveActionBarHideOffset);
632        removeCallbacks(mAddActionBarHideOffset);
633        if (mCurrentActionBarTopAnimator != null) {
634            mCurrentActionBarTopAnimator.cancel();
635        }
636        if (mCurrentActionBarBottomAnimator != null) {
637            mCurrentActionBarBottomAnimator.cancel();
638        }
639    }
640
641    private void postRemoveActionBarHideOffset() {
642        haltActionBarHideOffsetAnimations();
643        postDelayed(mRemoveActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY);
644    }
645
646    private void postAddActionBarHideOffset() {
647        haltActionBarHideOffsetAnimations();
648        postDelayed(mAddActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY);
649    }
650
651    private void removeActionBarHideOffset() {
652        haltActionBarHideOffsetAnimations();
653        mRemoveActionBarHideOffset.run();
654    }
655
656    private void addActionBarHideOffset() {
657        haltActionBarHideOffsetAnimations();
658        mAddActionBarHideOffset.run();
659    }
660
661    private boolean shouldHideActionBarOnFling(float velocityX, float velocityY) {
662        mFlingEstimator.fling(0, 0, 0, (int) velocityY, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
663        final int finalY = mFlingEstimator.getFinalY();
664        return finalY > mActionBarTop.getHeight();
665    }
666
667    @Override
668    public void setWindowCallback(Window.Callback cb) {
669        pullChildren();
670        mDecorToolbar.setWindowCallback(cb);
671    }
672
673    @Override
674    public void setWindowTitle(CharSequence title) {
675        pullChildren();
676        mDecorToolbar.setWindowTitle(title);
677    }
678
679    @Override
680    public CharSequence getTitle() {
681        pullChildren();
682        return mDecorToolbar.getTitle();
683    }
684
685    @Override
686    public void initFeature(int windowFeature) {
687        pullChildren();
688        switch (windowFeature) {
689            case Window.FEATURE_PROGRESS:
690                mDecorToolbar.initProgress();
691                break;
692            case Window.FEATURE_INDETERMINATE_PROGRESS:
693                mDecorToolbar.initIndeterminateProgress();
694                break;
695            case Window.FEATURE_ACTION_BAR_OVERLAY:
696                setOverlayMode(true);
697                break;
698        }
699    }
700
701    @Override
702    public void setUiOptions(int uiOptions) {
703        // Split Action Bar not included.
704    }
705
706    @Override
707    public boolean hasIcon() {
708        pullChildren();
709        return mDecorToolbar.hasIcon();
710    }
711
712    @Override
713    public boolean hasLogo() {
714        pullChildren();
715        return mDecorToolbar.hasLogo();
716    }
717
718    @Override
719    public void setIcon(int resId) {
720        pullChildren();
721        mDecorToolbar.setIcon(resId);
722    }
723
724    @Override
725    public void setIcon(Drawable d) {
726        pullChildren();
727        mDecorToolbar.setIcon(d);
728    }
729
730    @Override
731    public void setLogo(int resId) {
732        pullChildren();
733        mDecorToolbar.setLogo(resId);
734    }
735
736    @Override
737    public boolean canShowOverflowMenu() {
738        pullChildren();
739        return mDecorToolbar.canShowOverflowMenu();
740    }
741
742    @Override
743    public boolean isOverflowMenuShowing() {
744        pullChildren();
745        return mDecorToolbar.isOverflowMenuShowing();
746    }
747
748    @Override
749    public boolean isOverflowMenuShowPending() {
750        pullChildren();
751        return mDecorToolbar.isOverflowMenuShowPending();
752    }
753
754    @Override
755    public boolean showOverflowMenu() {
756        pullChildren();
757        return mDecorToolbar.showOverflowMenu();
758    }
759
760    @Override
761    public boolean hideOverflowMenu() {
762        pullChildren();
763        return mDecorToolbar.hideOverflowMenu();
764    }
765
766    @Override
767    public void setMenuPrepared() {
768        pullChildren();
769        mDecorToolbar.setMenuPrepared();
770    }
771
772    @Override
773    public void setMenu(Menu menu, MenuPresenter.Callback cb) {
774        pullChildren();
775        mDecorToolbar.setMenu(menu, cb);
776    }
777
778    @Override
779    public void saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) {
780        pullChildren();
781        mDecorToolbar.saveHierarchyState(toolbarStates);
782    }
783
784    @Override
785    public void restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) {
786        pullChildren();
787        mDecorToolbar.restoreHierarchyState(toolbarStates);
788    }
789
790    @Override
791    public void dismissPopups() {
792        pullChildren();
793        mDecorToolbar.dismissPopupMenus();
794    }
795
796    public static class LayoutParams extends MarginLayoutParams {
797        public LayoutParams(Context c, AttributeSet attrs) {
798            super(c, attrs);
799        }
800
801        public LayoutParams(int width, int height) {
802            super(width, height);
803        }
804
805        public LayoutParams(ViewGroup.LayoutParams source) {
806            super(source);
807        }
808
809        public LayoutParams(ViewGroup.MarginLayoutParams source) {
810            super(source);
811        }
812    }
813
814    public interface ActionBarVisibilityCallback {
815        void onWindowVisibilityChanged(int visibility);
816        void showForSystem();
817        void hideForSystem();
818        void enableContentAnimations(boolean enable);
819        void onContentScrollStarted();
820        void onContentScrollStopped();
821    }
822}
823