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 */ 16 package android.support.v7.widget; 17 18 import android.content.Context; 19 import android.content.res.TypedArray; 20 import android.os.Build; 21 import android.support.v4.content.res.ConfigurationHelper; 22 import android.support.v4.view.ViewCompat; 23 import android.support.v7.appcompat.R; 24 import android.util.AttributeSet; 25 import android.view.Gravity; 26 import android.view.View; 27 import 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 */ 35 public 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 ButtonBarLayout(Context context, AttributeSet attrs)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 setAllowStacking(boolean allowStacking)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 onMeasure(int widthMeasureSpec, int heightMeasureSpec)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 setStacked(boolean stacked)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 isStacked()132 private boolean isStacked() { 133 return getOrientation() == LinearLayout.VERTICAL; 134 } 135 } 136