19b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes/*
29b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes * Copyright (C) 2015 The Android Open Source Project
39b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes *
49b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes * Licensed under the Apache License, Version 2.0 (the "License");
59b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes * you may not use this file except in compliance with the License.
69b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes * You may obtain a copy of the License at
79b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes *
89b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes *      http://www.apache.org/licenses/LICENSE-2.0
99b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes *
109b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes * Unless required by applicable law or agreed to in writing, software
119b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes * distributed under the License is distributed on an "AS IS" BASIS,
129b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes * See the License for the specific language governing permissions and
149b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes * limitations under the License.
159b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes */
1666698bb15ba0f873aa1c2290cc50d6bb839a474aChris Banespackage android.support.v7.widget;
179b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes
189b8f708b391832e954167daecab05c5f1ee4cb6cChris Banesimport android.content.Context;
199b8f708b391832e954167daecab05c5f1ee4cb6cChris Banesimport android.content.res.TypedArray;
2019e78d4fcde0ec98661d1c278878357475b2816eChris Banesimport android.os.Build;
21c39d9c75590eca86a5e7e32a8824ba04a0d42e9bAlan Viveretteimport android.support.annotation.RestrictTo;
220cbe64051d4241ad38cb4a232a86e6f26a3c8259Chris Banesimport android.support.v4.content.res.ConfigurationHelper;
2319e78d4fcde0ec98661d1c278878357475b2816eChris Banesimport android.support.v4.view.ViewCompat;
249b8f708b391832e954167daecab05c5f1ee4cb6cChris Banesimport android.support.v7.appcompat.R;
259b8f708b391832e954167daecab05c5f1ee4cb6cChris Banesimport android.util.AttributeSet;
269b8f708b391832e954167daecab05c5f1ee4cb6cChris Banesimport android.view.Gravity;
279b8f708b391832e954167daecab05c5f1ee4cb6cChris Banesimport android.view.View;
289b8f708b391832e954167daecab05c5f1ee4cb6cChris Banesimport android.widget.LinearLayout;
299b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes
30c39d9c75590eca86a5e7e32a8824ba04a0d42e9bAlan Viveretteimport static android.support.annotation.RestrictTo.Scope.GROUP_ID;
31c39d9c75590eca86a5e7e32a8824ba04a0d42e9bAlan Viverette
329b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes/**
339b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes * An extension of LinearLayout that automatically switches to vertical
349b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes * orientation when it can't fit its child views horizontally.
359b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes *
369b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes * @hide
379b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes */
38c39d9c75590eca86a5e7e32a8824ba04a0d42e9bAlan Viverette@RestrictTo(GROUP_ID)
399b8f708b391832e954167daecab05c5f1ee4cb6cChris Banespublic class ButtonBarLayout extends LinearLayout {
402f2b4ac338abb059272e2b9ad8a59402f31e20c8Filip Gruszczynski    // Whether to allow vertically stacked button bars. This is disabled for
412f2b4ac338abb059272e2b9ad8a59402f31e20c8Filip Gruszczynski    // configurations with a small (e.g. less than 320dp) screen height. -->
422f2b4ac338abb059272e2b9ad8a59402f31e20c8Filip Gruszczynski    private static final int ALLOW_STACKING_MIN_HEIGHT_DP = 320;
439b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes
449b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes    /** Whether the current configuration allows stacking. */
459b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes    private boolean mAllowStacking;
469b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes    private int mLastWidthSize = -1;
479b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes
489b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes    public ButtonBarLayout(Context context, AttributeSet attrs) {
499b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        super(context, attrs);
502f2b4ac338abb059272e2b9ad8a59402f31e20c8Filip Gruszczynski        final boolean allowStackingDefault =
510cbe64051d4241ad38cb4a232a86e6f26a3c8259Chris Banes                ConfigurationHelper.getScreenHeightDp(getResources())
522f2b4ac338abb059272e2b9ad8a59402f31e20c8Filip Gruszczynski                        >= ALLOW_STACKING_MIN_HEIGHT_DP;
539b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
542f2b4ac338abb059272e2b9ad8a59402f31e20c8Filip Gruszczynski        mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking,
552f2b4ac338abb059272e2b9ad8a59402f31e20c8Filip Gruszczynski                allowStackingDefault);
569b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        ta.recycle();
579b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes    }
589b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes
599b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes    public void setAllowStacking(boolean allowStacking) {
609b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        if (mAllowStacking != allowStacking) {
619b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes            mAllowStacking = allowStacking;
629b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes            if (!mAllowStacking && getOrientation() == LinearLayout.VERTICAL) {
639b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes                setStacked(false);
649b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes            }
659b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes            requestLayout();
669b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        }
679b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes    }
689b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes
699b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes    @Override
709b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
719b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
729b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        if (mAllowStacking) {
739b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes            if (widthSize > mLastWidthSize && isStacked()) {
749b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes                // We're being measured wider this time, try un-stacking.
759b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes                setStacked(false);
769b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes            }
779b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes            mLastWidthSize = widthSize;
789b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        }
799b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        boolean needsRemeasure = false;
809b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        // If we're not stacked, make sure the measure spec is AT_MOST rather
819b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        // than EXACTLY. This ensures that we'll still get TOO_SMALL so that we
829b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        // know to stack the buttons.
839b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        final int initialWidthMeasureSpec;
849b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        if (!isStacked() && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
859b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes            initialWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
869b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes            // We'll need to remeasure again to fill excess space.
879b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes            needsRemeasure = true;
889b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        } else {
899b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes            initialWidthMeasureSpec = widthMeasureSpec;
909b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        }
919b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec);
929b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        if (mAllowStacking && !isStacked()) {
9319e78d4fcde0ec98661d1c278878357475b2816eChris Banes            final boolean stack;
9419e78d4fcde0ec98661d1c278878357475b2816eChris Banes
9519e78d4fcde0ec98661d1c278878357475b2816eChris Banes            if (Build.VERSION.SDK_INT >= 11) {
9619e78d4fcde0ec98661d1c278878357475b2816eChris Banes                // On API v11+ we can use MEASURED_STATE_MASK and MEASURED_STATE_TOO_SMALL
9719e78d4fcde0ec98661d1c278878357475b2816eChris Banes                final int measuredWidth = ViewCompat.getMeasuredWidthAndState(this);
9819e78d4fcde0ec98661d1c278878357475b2816eChris Banes                final int measuredWidthState = measuredWidth & ViewCompat.MEASURED_STATE_MASK;
9919e78d4fcde0ec98661d1c278878357475b2816eChris Banes                stack = measuredWidthState == ViewCompat.MEASURED_STATE_TOO_SMALL;
10019e78d4fcde0ec98661d1c278878357475b2816eChris Banes            } else {
10119e78d4fcde0ec98661d1c278878357475b2816eChris Banes                // Before that we need to manually total up the children's preferred width.
10219e78d4fcde0ec98661d1c278878357475b2816eChris Banes                // This isn't perfect but works well enough for a workaround.
10319e78d4fcde0ec98661d1c278878357475b2816eChris Banes                int childWidthTotal = 0;
10419e78d4fcde0ec98661d1c278878357475b2816eChris Banes                for (int i = 0, count = getChildCount(); i < count; i++) {
10519e78d4fcde0ec98661d1c278878357475b2816eChris Banes                    childWidthTotal += getChildAt(i).getMeasuredWidth();
10619e78d4fcde0ec98661d1c278878357475b2816eChris Banes                }
10719e78d4fcde0ec98661d1c278878357475b2816eChris Banes                stack = (childWidthTotal + getPaddingLeft() + getPaddingRight()) > widthSize;
10819e78d4fcde0ec98661d1c278878357475b2816eChris Banes            }
10919e78d4fcde0ec98661d1c278878357475b2816eChris Banes
11019e78d4fcde0ec98661d1c278878357475b2816eChris Banes            if (stack) {
1119b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes                setStacked(true);
1129b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes                // Measure again in the new orientation.
1139b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes                needsRemeasure = true;
1149b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes            }
1159b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        }
1169b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        if (needsRemeasure) {
1179b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1189b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        }
1199b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes    }
1209b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes
1219b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes    private void setStacked(boolean stacked) {
1229b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
1239b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        setGravity(stacked ? Gravity.RIGHT : Gravity.BOTTOM);
1249b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        final View spacer = findViewById(R.id.spacer);
1259b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        if (spacer != null) {
1269b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes            spacer.setVisibility(stacked ? View.GONE : View.INVISIBLE);
1279b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        }
1289b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        // Reverse the child order. This is specific to the Material button
1299b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        // bar's layout XML and will probably not generalize.
1309b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        final int childCount = getChildCount();
1319b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        for (int i = childCount - 2; i >= 0; i--) {
1329b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes            bringChildToFront(getChildAt(i));
1339b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        }
1349b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes    }
1359b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes
1369b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes    private boolean isStacked() {
1379b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes        return getOrientation() == LinearLayout.VERTICAL;
1389b8f708b391832e954167daecab05c5f1ee4cb6cChris Banes    }
1392f2b4ac338abb059272e2b9ad8a59402f31e20c8Filip Gruszczynski}
140