[go: nahoru, domu]

1/*
2 * Copyright (C) 2015 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 */
16package android.support.v7.widget;
17
18import android.content.Context;
19import android.content.res.TypedArray;
20import android.os.Build;
21import android.support.v4.content.res.ConfigurationHelper;
22import android.support.v4.view.ViewCompat;
23import android.support.v7.appcompat.R;
24import android.util.AttributeSet;
25import android.view.Gravity;
26import android.view.View;
27import android.widget.LinearLayout;
28
29/**
30 * An extension of LinearLayout that automatically switches to vertical
31 * orientation when it can't fit its child views horizontally.
32 *
33 * @hide
34 */
35public class ButtonBarLayout extends LinearLayout {
36    // Whether to allow vertically stacked button bars. This is disabled for
37    // configurations with a small (e.g. less than 320dp) screen height. -->
38    private static final int ALLOW_STACKING_MIN_HEIGHT_DP = 320;
39
40    /** Whether the current configuration allows stacking. */
41    private boolean mAllowStacking;
42    private int mLastWidthSize = -1;
43
44    public ButtonBarLayout(Context context, AttributeSet attrs) {
45        super(context, attrs);
46        final boolean allowStackingDefault =
47                ConfigurationHelper.getScreenHeightDp(getResources())
48                        >= ALLOW_STACKING_MIN_HEIGHT_DP;
49        final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
50        mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking,
51                allowStackingDefault);
52        ta.recycle();
53    }
54
55    public void setAllowStacking(boolean allowStacking) {
56        if (mAllowStacking != allowStacking) {
57            mAllowStacking = allowStacking;
58            if (!mAllowStacking && getOrientation() == LinearLayout.VERTICAL) {
59                setStacked(false);
60            }
61            requestLayout();
62        }
63    }
64
65    @Override
66    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
67        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
68        if (mAllowStacking) {
69            if (widthSize > mLastWidthSize && isStacked()) {
70                // We're being measured wider this time, try un-stacking.
71                setStacked(false);
72            }
73            mLastWidthSize = widthSize;
74        }
75        boolean needsRemeasure = false;
76        // If we're not stacked, make sure the measure spec is AT_MOST rather
77        // than EXACTLY. This ensures that we'll still get TOO_SMALL so that we
78        // know to stack the buttons.
79        final int initialWidthMeasureSpec;
80        if (!isStacked() && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
81            initialWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
82            // We'll need to remeasure again to fill excess space.
83            needsRemeasure = true;
84        } else {
85            initialWidthMeasureSpec = widthMeasureSpec;
86        }
87        super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec);
88        if (mAllowStacking && !isStacked()) {
89            final boolean stack;
90
91            if (Build.VERSION.SDK_INT >= 11) {
92                // On API v11+ we can use MEASURED_STATE_MASK and MEASURED_STATE_TOO_SMALL
93                final int measuredWidth = ViewCompat.getMeasuredWidthAndState(this);
94                final int measuredWidthState = measuredWidth & ViewCompat.MEASURED_STATE_MASK;
95                stack = measuredWidthState == ViewCompat.MEASURED_STATE_TOO_SMALL;
96            } else {
97                // Before that we need to manually total up the children's preferred width.
98                // This isn't perfect but works well enough for a workaround.
99                int childWidthTotal = 0;
100                for (int i = 0, count = getChildCount(); i < count; i++) {
101                    childWidthTotal += getChildAt(i).getMeasuredWidth();
102                }
103                stack = (childWidthTotal + getPaddingLeft() + getPaddingRight()) > widthSize;
104            }
105
106            if (stack) {
107                setStacked(true);
108                // Measure again in the new orientation.
109                needsRemeasure = true;
110            }
111        }
112        if (needsRemeasure) {
113            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
114        }
115    }
116
117    private void setStacked(boolean stacked) {
118        setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
119        setGravity(stacked ? Gravity.RIGHT : Gravity.BOTTOM);
120        final View spacer = findViewById(R.id.spacer);
121        if (spacer != null) {
122            spacer.setVisibility(stacked ? View.GONE : View.INVISIBLE);
123        }
124        // Reverse the child order. This is specific to the Material button
125        // bar's layout XML and will probably not generalize.
126        final int childCount = getChildCount();
127        for (int i = childCount - 2; i >= 0; i--) {
128            bringChildToFront(getChildAt(i));
129        }
130    }
131
132    private boolean isStacked() {
133        return getOrientation() == LinearLayout.VERTICAL;
134    }
135}
136