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