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