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